I recently worked on a rWhois project that required writing an ASP.NET MVC 4 based admin portal. One of the last things I tackled was adding validation to inputs. Adding validation to a MVC model is super-simple with Data Annotations. By adding these annotations to your model, both client and server-side validation comes to life. There’s a great tutorial describing how to accomplish this on the asp.net site:
http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/adding-validation-to-the-model
The in-box DataAnnotations class provides numerous built-in validation attributes that can be applied to any property. Things like strings of specific length, or integers within a range, or even regex matching is supported using the built-in validation attributes – and they are automatically wired up when you use Visual Studio to add views with scaffolding. But what about when the built-in attributes don’t quite do what you need?
I needed to create validation that confirms the input is both in proper CIDR format (ie. 10.0.0.0/8 and not “some text”) and that the supplied CIDR subnet is valid (ie. 192.168.100.0/24 is a proper network/bitmask, but 192.168.100.50/24 is not). Validating that the input is in the proper format can easily be done with regex. Just add the following to annotation to the appropriate property on your model:
[RegularExpression(@"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$", ErrorMessage="Not valid CIDR format")] |
To check that the supplied input is a valid network/bitmask CIDR combination, I needed custom validation. You can easily extend the ValidationAttribute and implement IClientValidatable to add your custom validation. First, you’ll want to create a new class and reference the DataAnnotations and Mvc namespaces:
using System.ComponentModel.DataAnnotations; using System.Web.Mvc; namespace MvcApplication1.Validation { public class CIDRSubnet : ValidationAttribute, IClientValidatable { } } |
Next, we’ll want to create the appropriate server-side validation by overriding the ValidationResult class and adding the CIDR subnet check:
protected override ValidationResult IsValid(object value, ValidationContext validationContext) { //validate that valus is valid cdir string[] cidr = ((string)value).Split(new char[] { '/' }); string[] ip = cidr[0].Split(new char[] { '.' }); int i = (Convert.ToInt32(ip[0]) << 24) | (Convert.ToInt32(ip[1]) << 16) | (Convert.ToInt32(ip[2]) << 8) | (Convert.ToInt32(ip[3])); int mask = Convert.ToInt32(cidr[1]) == 0 ? 0 : ~((1 << (32-29)) - 1); int network = i & mask; if (i != network) return new ValidationResult("Not a valid CIDR Subnet"); return ValidationResult.Success; } |
Now we want to create our client side validation function. Create a new javascript file and make sure it’s referenced in your View:
function ipv4checker(input) { //confirm cidr is valid network var cidr = input.split('/'); var base = cidr[0]; var bits = cidr[1]; var ip = base.split('.'); var i = (ip[0] << 24 | ip[1] << 16 | ip[2] << 8 | ip[3]); var mask = bits == 0 ? 0 : ((1 << (32 - bits)) - 1) ^ 0xFFFFFFFF; var network = i & mask; if (i == network) { return true; } else { return false; } } |
Assuming jquery and jquery unobtrusive validation libraries have already been added to your project, you can then add a method to the validator, and wire it up. I’m not passing any parameters here, but you could also include parameters:
//custom vlidation rule - check IPv4 $.validator.addMethod("checkcidr", function (value, element, param) { return ipv4checker(value); }); //wire up the unobtrusive validation $.validator.unobtrusive.adapters.add ("checkcidr", function (options) { options.rules["checkcidr"] = true; options.messages["checkcidr"] = options.message; }); |
Then, you’ll need to implement the GetClientValidationRules method in your custom validation class:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { ModelClientValidationRule rule = new ModelClientValidationRule(); rule.ValidationType = "checkcidr"; rule.ErrorMessage = "Not a valid CIDR Subnet"; return new List<ModelClientValidationRule> { rule }; } |
Lastly, add a reference to your custom validation class in the model and add the custom validation attribute to your property:
using System.ComponentModel.DataAnnotations; using MvcApplication1.Validation; namespace MvcApplication1.Models { public class Allocation { [Required] [RegularExpression(@"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$", ErrorMessage="Not valid CIDR format")] [CIDRSubnet(ErrorMessage="Not a valid CIDR Subnet")] public string IPNetwork { get; set; } } } |