ReBuildAll Blog
Thoughts (mostly) on .NET development

Extending MVC validation with new rules   (MVC)   
Did you ever want to extend MVC validation with new rules? For example in my work I need to validate fields that contain Finnish social security numbers, business identification numbers or IBAN bank account numbers. These have a well defined format. They also have check digits/numbers that can be used to verify if the number is actually valid.

While I could use simply regular expressions to verify the format itself, it is not possible to validate the check digits / numbers without actually coding them.

If you have used data annotations to create your ASP.NET MVC models, you know that it is very easy to add different kinds of validation rules to your data model. ASP.NET MVC will take care of generating all the needed client side validation logic for you (which uses jQuery Validation) and also makes sure the data is validated on the server side as well.

In this article I will show how to extend this mechanism and provide easy to use client and server side custom validation using the same mechanics. I will use MVC 4 in this article, but the core validation works with MVC 3 as well (some examples use bundles, which were not in MVC 3, but the actual validation logic will work with MVC 3).

Javascript validation
Let us start by creating a validation method for IBAN account numbers in Javascript. We want to extend the jQuery Validation framework that comes with ASP.NET MVC project template. The following code directly adds a new validation method to jQuery Validation. First the code, then the explanation:
$.validator.addMethod("iban", function (value, element) {
    var conversion = {
        "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "G": 16,
        "H": 17, "I": 18, "J": 19, "K": 20, "L": 21, "M": 22, "N": 23,
        'O': 24, "P": 25, "Q": 26, "R": 27, "S": 28, "T": 29, 'U': 30,
        "V": 31, "W": 32, "X": 33, "Y": 34, "Z": 35
    };
    var numbers = "0123456789";

    var isValidIBAN = false;
    var ibanregex = /^([A-Z]{2}\d{2})\s?([A-Z0-9]{4}\s?)*([A-Z0-9]{1,4})$/;

    if (ibanregex.test(value)) {
        var iban = value.replace(/\s+/g, "").toUpperCase();
        iban = iban.substr(4) + iban.substr(0, 4);
        var convertediban = "";
        for (var i = 0; i < iban.length; ++i) {
            if (numbers.indexOf(iban.charAt(i), 0) == -1) {
                convertediban += conversion[iban.charAt(i)];
            }
            else {
                convertediban += iban.charAt(i);
            }
        }
        var reminder = 0;
        while (convertediban.length > 0) {
            var p = convertediban.length > 7 ? 7 : convertediban.length;
            reminder = parseInt(reminder.toString() + convertediban.substr(0, p), 10) % 97;
            convertediban = convertediban.substr(p);
        }

        if (reminder == 1) {
            isValidIBAN = true;
        }
    }

    return this.optional(element) || isValidIBAN;
}, "Check your IBAN number!");

$.validator.unobtrusive.adapters.addBool("iban");

The addMethod method will add our validation logic to jQuery Validate. This new function will receive the value to be validated. The actual validation logic might not be the nicest Javascript code, but what it does is check the value if it is indeed a IBAN number. I included a regular expression that does the format checking as well, so I do not need to add that separately.

After the jQuery Validate method is added, we also register the iban validation method with the jQuery Unobtrusive Validation framework. The addBool method adds a new validation that returns true/false based on the result.

Ideally you would put this code into your own Javascript file (for example, customvalidation.js). You need to include this AFTER jQuery, jQuery Validation and jQuery Unobtrusive Validation .js files have been included in your code. In a MVC view, you could add it as follows:
@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
    <script src='@Url.Content("~/Scripts/customvalidation.js")' />
}

And voila, client side IBAN account validation is now available to your HTML code. Add the data-val-iban attribute to the element where you want to test it. The following ASP.NET MVC sample demonstrates how to create a simple form with one field, that has iban validation enabled on the client side:
@using(Html.BeginForm())
{
    <div>
        <input type="text" name="ibanaccount" id="ibanaccount"
            data-val="true" data-val-iban="Check your IBAN number!" 
            data-val-required="Required"  />
        @Html.ValidationMessage("ibanaccount")
    </div>

    <div>
        <input type="submit" value="Submit" />
    </div>
}

I created the input field manually by typing HTML code and adding all the validation attributes. I exploited MVC's ValidationMessage() method to generate the needed span to display a error message in case validation fails.

You can use the following sample IBAN number from Wikipedia to test: GB29 NWBK 6016 1331 9268 19. Or just enter your own IBAN number :) The validation does not even require the spaces, so you can freely omit those if you like.

Our goal is however, that you would not need to write the attributes manually. Instead we want this to integrate with Data Annotations attributes.

Server side validation
In order to create a new server side validation we will derive a class from ValidationAttribute. To tell MVC that we support client side validation the class will also implement IClientValidatable.

The attribute will contain both the server side logic and the code needed to use the Javascript validation we created above.

Let's create the skeleton of the class:
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
    public sealed class IBANAttribute : ValidationAttribute, IClientValidatable
    {
    }

Lets start with the client side validation first. The IClientValidatable interface defines a single member method that returns an enumerable collection of client side rules. We will use yield return to return a single rule:
        public IEnumerable<ModelClientValidationRule> 
                  GetClientValidationRules(ModelMetadata metadata, 
                             ControllerContext context)
        {
            yield return new ModelClientValidationRule()
            {
                ErrorMessage = "Check IBAN account number!",
                ValidationType = "iban"
            };
        }

The rule simply states what error message we would want and what is the validation type. The rule will generate a data-val-iban attribute similar to what we had in our HTML before on the client side.

Finally, lets create the server side validation method.
        public override bool IsValid(object value)
        {
            if (value == null) return true;

            Dictionary<char,int> conversion = new Dictionary<char,int>();
            conversion.Add('A', 10);
            conversion.Add('B', 11);
            conversion.Add('C', 12);
            conversion.Add('D', 13);
            conversion.Add('E', 14);
            conversion.Add('F', 15);
            conversion.Add('G', 16);
            conversion.Add('H', 17);
            conversion.Add('I', 18 );
            conversion.Add('J', 19);
            conversion.Add('K', 20);
            conversion.Add('L', 21);
            conversion.Add('M', 22);
            conversion.Add('N', 23);
            conversion.Add('O', 24);
            conversion.Add('P', 25);
            conversion.Add('Q', 26);
            conversion.Add('R', 27);
            conversion.Add('S', 28 );
            conversion.Add('T', 29);
            conversion.Add('U', 30);
            conversion.Add('V', 31);
            conversion.Add('W', 32);
            conversion.Add('X', 33);
            conversion.Add('Y', 34);
            conversion.Add('Z', 35);

            bool isValidIBAN = false;
            Regex ibanregex = new Regex(@"^([A-Z]{2}\d{2})\s?([A-Z0-9]{4}\s?)*([A-Z0-9]{1,4})$");

            if (ibanregex.IsMatch(value.ToString())) {
                var iban = value.ToString().Replace(" ", "").ToUpper();
                iban = iban.Substring(4) + iban.Substring(0, 4);
                var convertediban = "";
                for (var j = 0; j < iban.Length; ++j) {
                    if ( char.IsDigit ( iban[j] ) == false ) {
                        convertediban += conversion[iban[j]];
                    }
                    else {
                        convertediban += iban[j];
                    }
                }
                var reminder = 0;
                while (convertediban.Length > 0) {
                    var p = convertediban.Length > 7 ? 7 : convertediban.Length;
                    reminder = int.Parse(reminder.ToString() + convertediban.Substring(0, p)) % 97;
                    convertediban = convertediban.Substring(p);
                }

                if (reminder == 1) {
                    isValidIBAN = true;
                }
            }

            return isValidIBAN;
        }

Notice how similar the code is to the Javascript version (thanks to the var keyword, for example). Except maybe the dictionary filling code, which is longer to do in C#. The method check the format using the same regex, and then validates the argument. It returns a boolean value based on the result.

This is all we needed to have both client and server side validation added for IBAN account numbers. Similar methods can be used to add validation to just about any kind of custom validation logic. As a last step, we will test how this works.

Putting our new attribute onto a model
Lets create a new model to test our validation logic.
    public class SimpleTestModel
    {
        [Required]
        [MaxLength(50)]
        public string Name { get; set; }

        [IBAN]
        [Required]
        public string AccountNumber { get; set; }
    }

And create a new action method that will use this model and also accept it back on POST requests.
        [HttpGet]
        public ActionResult TestIban()
        {
            SimpleTestModel model = new SimpleTestModel();
            return View(model);
        }

        [HttpPost]
        public ActionResult TestIban(SimpleTestModel model)
        {
            if (ModelState.IsValid)
            {
                // save
                // ..

                return RedirectToAction("Index");
            }
            else
            {
                return View(model);
            }
        }

And finally add a view. I will only show you the part that renders the account number field. You can easily generate the view based on the model class using MVC's scaffolding feature (use the Create or Edit template).
        <div class="editor-label">
            @Html.LabelFor(model => model.AccountNumber)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.AccountNumber)
            @Html.ValidationMessageFor(model => model.AccountNumber)
        </div>

As you can see, it does not differ from any other field you would have used previously in MVC. And now when you try this, you will see client side validation working. If you want to test server side validation you need to either disable the client side validation (just remove the interface from the attribute class) or use a tool like Fiddler to manipulate the request before it gets to the server.

Summary
Adding client and server side validation to ASP.NET MVC is an easy task if you follow the steps outlined here. You can add as many different kinds of validations as you need.

 

Comments

Abdul Wahab Re: Extending MVC validation with new rules
Hi,

It's not working in my case.. could you please help out ?
Evert Wiesenekker Re: Extending MVC validation with new rules
Great post and very helpful!
Lenard Gunda Re: Extending MVC validation with new rules
Glad I could help! :)
Piotr Re: Extending MVC validation with new rules
Excellent example from the real world.
Exactly what I needed so it saved me some work ;)