Practical .NET

Adding Client-Side Validation in ASP.NET MVC 3

By having your data annotations implement the IClientValidatable interface, you can make it easy for developers to integrate your client-side validation into your Views.

The goal of the IClientValidatable interface is to let you extend your server-side DataAnnotations with client-side functions in ASP.NET MVC 3. For instance, a Data Annotation that ensured that a field doesn't exceed a specified length might look like the following example (see my column on Data Annotations. That Data Annotation would have, as this example does, a MaxLength property that allows the developer using the annotation to specify the maximum number of characters allowed:

Public Class MaxLengthAttribute
    Inherits ComponentModel.DataAnnotations.ValidationAttribute

    Public Property MaxChars As Integer

        //...code to check for errors
End Class

A developer could invoke this validation code just by adding the Data Annotation to the ModelView class that is passed to a View, like this:

Public Class Customer

  <MaxLength(MaxChars:=25)>
  Public Property CustomerID() As String

Extending this Data Annotation's validation to the client in ASP.NET MVC 3 requires just three additional components: the JavaScript code (which must be registered with ASP.NET MVC's client-side framework) , a class to pass parameters from the Data Annotation to the JavaScript function, and some wire up code in your Data Annotation.

Registering the JavaScript Function
The first step is to create a JavaScript function that registers a validation function with ASP.NET MVC's client-side list of validation functions. Your registration function is passed a set of parameters, set on the server, which you can extract for use in your validation function. You'll also be passed the error message associated with the validation function which you must extract into a variable called “message”. The following example creates a new function and registers it under the name MaxLength. It extracts, from the parameter, the error message and the maximum number of characters allowed:

Sys.Mvc.ValidatorRegistry.validators.MaxLength = function (parms) {
  var MaxChars = parms.ValidationParameters.MaxChars;
  var message = parms.ErrorMessage;

The next part of your registration function returns the actual validation function. This function is passed two parameters: the value to be checked and some additional data. The function must return false if an error is found, true otherwise.

return function (value, context) 
         {   
           if (value.length > MaxChars) 
           {
             return false; 
           }
	   else
	   {
             return true;
	   }
         };
       };

While you could put this code in your View, it would be better to put it in a script library. Not only does this support unobtrusive JavaScript, it makes it easier for a developer to use your function: The developer just needs to add a reference to your script library.

Adding Server-side Support
The next step is to create a class that specifies the JavaScript function to use and pass the parameters the function expects. For this, you create a server-side class that inherits from ModelclientValidationRule. In this class you must set the class' ValidationType to the name that your function is registered under on the client.

You must also set the parameters that will be passed to your validation function by adding name/value pairs to the class' ValidationParameters collection. You'll need to use the same names here that you use when retrieving the values in your client-side function.

This example sets up for the client-side function I showed earlier by setting the ValidationType to MaxLength (the name I used to store my client-side function) in the constructor and providing a property to add the MaxChars parameter to the ValidationParameters collection:

Public Class MaxLengthParms
    Inherits ModelClientValidationRule

    Public Sub New()
        Me.ValidationType = "MaxLength"
    End Sub

Public Property MaxCharacters() As Integer Get Return Me.ValidationParameters("MaxChars") End Get Set(value As Integer) Me.ValidationParameters.Add("MaxChars", value) End Set End Property

End Class

While it's certainly possible that this class could be used with a variety of Data Annotations, it seems unlikely (at least, to me). Because of that, I'd be tempted to put this class in the same file with the Data Annotation it supports.

The final step is to wire up the Data Annotation to the class that specifies the function and passes the parameters to it. After adding the IClientValidatable interface to your Data Annotation, you'll have a new method called GetClientValidationRules that's expected to return a collection of ModelClientValidationRule objects:

Public Class MaxLengthAttribute
    Inherits ComponentModel.DataAnnotations.ValidationAttribute
    Implements IClientValidatable
    
  Public Function GetClientValidationRules(
            metadata As ModelMetadata, 
            context As ControllerContext)  _
          As IEnumerable(Of ModelClientValidationRule) _
          Implements IClientValidatable.GetClientValidationRules

  End Function

From this method you can return a collection of ModelClientValidationRule objects (one for every JavaScript function you want to tie to this Data Annotation). In my case, since I'm only tying this Data Annotation to a single JavaScript function, that's just my MaxLengthParm. The first step, therefore, is to create the collection and instantiate the ModelClientValidationRule object that ties the Data Annotation to a JavaScript function:>

Dim mcvrs As New List(Of ModelClientValidationRule)
Dim mlp As New MaxLengthParms

The next step is to set the properties on the ModelClientValidationRule class. Ideally, most of those properties can be set from corresponding properties on the Data Annotation class it's part of. Your ModelClientValidationRule class will have inherited the ErrorMessage parameter, which you can set from the Data Annotation's ErrorMessage property, for instance. This example uses the MaxChars property I put in the Data Annotation to set the corresponding property on my ModelClientValidationRule class:

mlp.ErrorMessage = Me.ErrorMessage
mlp.MaxCharacters = Me.MaxChars

The final step is to add your ModelClientValidationRule object to the collection and return it:

mcvrs.Add(mlp)
Return mcvrs

Developers who use your Data Annotation (which might even include you) will now automatically get your client-side validation, provided they add a script reference to the library containing your JavaScript code and enables client-side validation by adding this tag to your View:

@Html.EnableClientValidation

But, with that minimal participation on the developer's  part, you've removed some processing form your server while the user gets their data right, and made for a more responsive application.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube