Unobtrusive client validation in ASP.NET MVC 3

Recently I walked through the pains of setting up flexible client-side validation on ASP.NET MVC 3 project. After all the hassle, the solution is actually pretty easy and elegant. All the information is available on the internet already but the One And Only blog post that explains it everything seems to be missing. So here it is.

What we want to achieve:

  • We do not want to duplicate validation rules into many places
  • We want to have as much as possible validated on the client
  • We do not want to have tons of boilerplate custom javascript

And the solution consists of the following elements:

  • ValidationAttributes (System.ComponentModel.DataAnnotations)
  • jQuery Validate
  • Unobtrusive extension to jQuery Validate

The basics of attribute-based validation

Namespace System.ComponentModel.DataAnnotations contains many attributes that you can use to validate user input. Specifically, you have DataTypeAttribute, RangeAttribute, RegularExpressionAttribute, RequiredAttribute and StringLengthAttribute. I guess the names are pretty self-explanatory.

In addition, System.Web.Mvc namespace contains CompareAttribute which allows you to define that the values of two different fields must be the same (for example, password and passwordConfirmation) and RemoteAttribute which allows you to have complex validation rules executed on the client. In practice, you can define a controller and action which is executed by an AJAX call.

ScottGu explains the basics pretty well: http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx.

Unobtrusive client validation

ScottGu’s example uses Microsoft’s own validation framework (MicrosoftMvcValidation.js) but in ASP.NET MVC 3 Microsoft allows us to use jQuery Validate and has implemented a nice addition on top of that. Brad Wilson posted a nice article about enabling unobtrusive validation: http://bradwilson.typepad.com/blog/2010/10/mvc3-unobtrusive-validation.html.

Extending the validation

It’s likely that sooner or later you find yourself having a custom validation rule that is not available in the previously mentioned framework. Most probably you’ll be missing the attribute on server side as jQuery Validate plugin actually contains a lot more validation methods than .NET Framework’s attributes represent, for example email validation for which the support is implemented below. You can find the list of all supported validation rules on the jQuery Validate’s documentation.

Custom attribute

It turns out that implementing a custom attribute is really an easy task. You implement your own class that inherits System.ComponentModel.DataAnnotations.ValidationAttribute and implements System.Web.Mvc.IClientValidatable. So you need to do three things.

1) Override public bool IsValid(object value)
This method will be run when the validation is done on the server (for example, if the client does not have javascript enabled). This is all you need to do if you don’t need client validation.

2) Create a class that inherits from ModelClientValidationRule. This is usually very simple. Here’s an example how to enable email validation on the client:

public class ModelClientValidationEmailRule : ModelClientValidationRule
{
    public ModelClientValidationEmailRule(string errorMessage)
    {
        base.ErrorMessage = errorMessage;
        base.ValidationType = "email";
    }
}

3) Implement public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)

This is also usually very easy to implement, here’s the example on email validation:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    yield return new ModelClientValidationEmailRule(FormatErrorMessage(metadata.GetDisplayName()));
}

This is all you need do to write your own attribute to enable validation using the readymade validation rules on jQuery Validate plugin. But there will be times when you invent new ways to do validation on client so you’ll need to write your own validation method for jQuery Validate.

Custom client validator

As you can guess, custom validators are not that hard to implement either. In the simple case (your validator doesn’t have any parameters) here’s all you need:

(function ($) {
    $.validator.addMethod(
        "your-method-name",
        function (value, element) {
            return (!value && this.optional(element)) || /*place your validation logic here*/;
        },
        "your own error message");
    $.validator.unobtrusive.adapters.addBool("your-method-name");
} (jQuery));

And to use this, you need to write your own custom attribute (and when implementing the ModelClientValidationRule, you need to set base.ValidationType = “your-method-name”).

NOTE: Usually it is good practice to first check whether the input is empty and whether the element is not marked as required. Otherwise using your own validator would also make the field required which is unexpected. The same applies to implementing server-side attribute’s IsValid method.

Conclusion and FAQ

This way we have a single solution for performing validation both on the client and on the server, and the usage of HTML 5 (yay!!!) data attributes for this kind of tasks is simply beautiful.

Q1: I have everything set up, but I still don’t get the validation happening on the client, it still goes to the server everytime.

A1.1: You do not have client validation enabled. Check that you have the following values on your web.config:

<appSettings>
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

A1.2: You do not have the required javascript libraries loaded. You need to have jquery, jquery.validate and jquery.validate.unobtrusive included on your page.

<script src="/Scripts/jquery-1.4.3.min.js" type="text/javascript" />
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"/>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"/>

Q2: I have validation rules that I cannot express by static attributes. How to use more complex validation rules?

A2: On your action method you can have arbitrary validation logic. To put the error messages into the same output system, use ModelState.AddModelError("<fieldname>", "ErrorMessage");


Tags: ,