MVC custom validator with client side validation

By on (tags: mvc, categories: code)

I wanted to have a reusable email validator since there have been a few cases already where I needed one. Previously I just used the RegularExpressionAttribute but that’s verbose for email. So, without thinking much, I created an attribute that derived from RegularExpressionAttribute and supplied it with a regex I used – that turned out to be a surprise.

The server side validation worked as expected, but client side validation didn’t work at all. That was a bit weird, because if I used a RegularExpressionAttribute it worked ok, so I started to dig a little deeper.

For MVC 3 I found out about 2 ways to handle client side validation in custom validators – first (and easiest) is to implement IClientValidatable on your attribute. The second is to use an adapter that derives from DataAnnotationsModelValidator<T> (or in general ModelValidator). Additionally you need client side code, but that’s the same in both cases.

Let’s take a look at the two options.

IClientValidatable

This interface has one method that you need to implement - GetClientValidationRules. That method gives some basic information about the validator such as the error message, parameters or the name of client side rule. For the email attribute it looks like this:

   1: public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
   2: {
   3:     yield return new ModelClientValidationRegexRule(ErrorMessage, Pattern);
   4: }

Not much, and that’s good. Note that this returns ModelClientValidationRegexRule. We would achieve the same effect by applying the following code, though that’s a bit more verbose:

   1: public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
   2: {
   3:     var rule = new ModelClientValidationRule
   4:     {
   5:         ValidationType = "regex",
   6:         ErrorMessage = ErrorMessage,
   7:     };
   8:     rule.ValidationParameters.Add("pattern", Pattern);
   9:     
  10:     yield return rule;
  11: }

DataAnnotationsModelValidator

This is actually how the built in validators are implemented – as the attributes come from an assembly that does not know about MVC, the client side validation code needs to be separated.

Here’s s sample:

   1: public class EmailAttributeAdapter : DataAnnotationsModelValidator<EmailAttribute>
   2: {
   3:     public EmailAttributeAdapter(ModelMetadata metadata, ControllerContext context, EmailAttribute attribute) : base(metadata, context, attribute)
   4:     {
   5:     }
   6:  
   7:     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
   8:     {
   9:         yield return new ModelClientValidationRegexRule(ErrorMessage, Attribute.Pattern);
  10:     }
  11: }

Looks familiar, doesn’t it.

Now you need to tell MVC that you have something that will help on the client side when the attribute is used. Here’s how you do it:

   1: DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAttribute), typeof(EmailAttributeAdapter));

The best place to put this code would probably be Application_Start. And this is actually the reason why the EmailAttribute didn’t work on the client at first. Just deriving from an attribute that supports client side validation, doesn’t mean that the framework knows how to handle the client side. I could register the attribute with an already existing adapter (the one regex validator uses – RegularExpressionAttributeAdapter) and that should work, though I didn’t try it – I wanted to learn how to do a totally custom thing.

Client code

So there’s one thing remaining – client code. There are 2 things we need to know: first there’s jquery.validate – that’s a jquery (surprise, surprise) plugin used for validation. Then there’s jquery.validation.unobtrusive which is kind of a plugin for a plugin (this one published by microsoft) to help with validation. This information is going to be useful in just a second.

Let me give you the code first and then explain what’s what (this is a sample for another attribute that I created – the reason is that for the email attribute, the client side validator is the one called “regex” as stated with the ValidationType property. That’s already there, so no point in copying it. In hindsight, I think I may have used the email validator as well – duh – as it’s built in the jquery.validate plugin)

   1: (function ($) {
   2:  
   3:     $.validator.addMethod('acceptancerequired', function (value, element, params) {
   4:         return $(element).is(':checked');
   5:     });
   6:  
   7:     $.validator.unobtrusive.adapters.addBool('acceptancerequired');
   8:  
   9: })(jQuery)

In line 3 you can see the definition of a method responsible for validation. The first parameter value must match with the ValidationType you returned in the attribute/adapter. You put your validation logic here. Note that this alone will not work as now, you need to tell the jquery.validate.unobtrusive to handle your custom validator. That’s exactly what’s happening in line 7 – you tell the unobtrusive plugin to add your validator to the validate plugin (phew).

Summary

Making a custom validator work on the server is easy, making it work on the client – a bit more complicated, but as you can see, It’s not much code.

Here and here are some nice and much more detailed articles about the subject which I used to learn.

Cheers