ASP.NET MVC3 and custom validation – a tale of ModelClientValidationRules, unobtrusive javascript, jQuery validation and poo

Creating custom validation for an ASP.NET MVC 3 application that executes both server-side and client-side is a rather frustrating experience. This is in part because of the hoops you have to jump through to make your code hook into the unobtrusive javascript framework that Microsoft have created (which is itself a layer on top of the jQuery Validation plugin), however it is equally hindered by the lack of clear documentation for anything other than trivial examples. Hopefully this will help you get to grips with what you need to do and give you enough information that you understand how the various components hang together. If you finish reading this and end up questioning the very convoluted method that makes this all work for something that you really need to bang out and release in double quick time, I encourage you to simply hook into the jQuery validation plugin on the client side in the first instance and skip the Microsoft abstractions.

It is no coincidence that for our example we are going to be creating a Monkey model that likes to fling poo – because the frustrations in coding this really does make you wish you could throw your toys and other things. As you go through putting this into your project, you will probably, at some point utter “WTF” louder than your work mates think is acceptable. Once or twice, it might be very therapeutic to go outside, pick up something heavy and smash the hell out of a few pieces of waste wood that you have lying around. At least, that worked for me.

Setting up the basic model, controller and views

Let’s begin with a new model. I’m going to call it Monkey. It looks like this:

public class Monkey
    {
        public int Id { get; set; }
        public string FlingPoo { get; set; }
        public string FlingToy { get; set; }
    }

Hit F6 to compile. Now, generate our Controller – right mouse button on the Controllers folder and select Add -> Controller. Fill in the details and hey presto. One Monkey controller, one set of Monkey Views. Wunderbar!

Laying out our custom validation requirement

For the sake of this example, I’ve decided that when we add a new Monkey, we have to fill in either Fling Poo or Fling Toy….or both….but we cannot leave both empty. In a fit of refactoring later on, you can make these yes/no drop downs, but I really suggest working with basic textboxes to keep it simple while you figure out what’s actually happening.

Data Annotations

ASP.NET MVC 3 comes with a bunch of built in validation which you enable by using Data Annotations. These are neat little bits of code with which you can decorate your Model’s properties. Once they are there, they carry a lot of weight – they create both server side and client side validation code automatically. For example, an annotation to make a poperty “required” would look like this in your model (remember to include System.ComponentModel.DataAnnotations or it won’t work):

[Required]
public string MonkeyName { get; set;}

If you’ve never worked with Data Annotations, go play with them now and then come back.

ValidationAttribute – our custom server-side validator

Validating our model server-side is the first step on the way to custom validation. Create a new folder called “Validation”. Create a new file inside said folder. Call it MustFlingSomethingValidator. Make it look something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;

namespace ChunkyMonkey.Validation
{
    public class MustFlingSomethingValidator:ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            return new ValidationResult("A monkey must fling something");
        }
    }
}

I like to make my custom validation fail all the time in the first instance – once I have an end-to-end framework in place, I’ll go back and write the actual validation logic (especially in this example because the validation logic is trivial in nature). Once you’ve added the above code, compile (F6) and then open up your Monkey model. Include the validation namespace in your model and add the new Annotation to your properties:

public class Monkey
    {
        public int Id { get; set; }
  
        [MustFlingSomethingValidator]
        public string FlingPoo { get; set; }

        [MustFlingSomethingValidator]
        public string FlingToy { get; set; }
    }

And run. Now you should have a failing validation on each textbox which will fire when you hit the Create button:

monkey-server-validation

The role of jQuery and Unobtrusive Javascript

This is only half the story, though, because at the moment, our validation is only fired server-side. We want to avoid the roundtrip if we can and bring in some client side validation. All hail jQuery. Microsoft have included jQuery with the MVC framework now and inluded with that is the jQuery Validation Plugin. Neat. Microsoft have also included their own validation abstraction called Unobtrusive Validation which sits on top of the jQuery Validation Plugin and provides the glue between the server-side data annotations and the client side Validation Plugin. Essentially the Unobtrusive Validation automatically adds rules and custom validation methods to the jQuery Validation plugin.

We are going to leverage the Unobtrusive Validation layer to bring our validations client-side. There are a few steps to this process and feels a bit cumbersome when you set it up for the first time. In truth, it is a bit cumbersome, and I think a streamlined process here would be nice, but I guess automatically porting c# validation into javascript is a bit too much to ask for. I’m going to start with a nice simple implementation and then we’ll refactor a little later. I’m also going to do this a bit backwards from the way most tutorials show it and start with the javascript side of things:

Three steps to client side validation

Step 1 – Create your custom jQuery Validator Method
Add a new javascript file in your “scripts” directory (or subdirectory thereof if you prefer to keep all your js out of the way of the default js provided). I’ve called mine mustFlingSomething.js.

$(function() {
    jQuery.validator.addMethod('mustflingsomething',
    function (value, element, params) {
        return false;
    }, '');

    jQuery.validator.unobtrusive.adapters.add('mustflingsomething',
    function (options) {
        options.rules['mustflingsomething'] = {
          };
        options.messages['mustflingsomething'] = options.message;
    });
}(jQuery));

This can be confusing the first time you do it because you have to write a little code to interface with both the jQuery Validator Plugin and you also have to tell the unobtrusive validation to use this new method and pass it any parameters you want passed in. We use the jQuery Validator Plugin’s addMethod to create a custom method of our own that will actually perform the validation logic for us. At the moment, we just return false so we can always fail the validation. Then we add an adapter to our unobtrusive validation, telling it that there is this new method called “mustflingsomething” that we want to use. We also pass in some rules and a message. The rules allow us to define an array of parameters that we want passed to the “mustflingsomething” function. The message is our error message.

Step 2 – Add your custom javascript to your view. Editing Create.html in my ViewsMonkey folder, the first few lines should look somethnig like this:

@model ChunkyMonkey.Models.Monkey
@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/custom/mustFlingSomething.js")" type="text/javascript"></script>

Step 3 – Make your validator support client side validation
Open your MustFlingSomethingValidator and add the IClientValidatable interface to your class definition:

public class MustFlingSomethingValidator:ValidationAttribute, IClientValidatable

Good, now add the following method

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            ModelClientValidationRule mvr = new ModelClientValidationRule();
            mvr.ErrorMessage = "Hey, you must fling something - you are a monkey after all";
            mvr.ValidationType = "mustflingsomething";

            return new[] {mvr};
        
        }

This piece of code is responsible for telling the runtime that you want to enable client-side validation on your particular input field. If you run your code and view source, you’ll notice that your inputs have some extra HTML5 attributes added to them. These attributes are written out because of the ModelClientValidationRule you’ve put together. They also get picked up by the unobtrusive javascript adapter and fed into the jQuery Validation function that you set up earlier.

You’ll notice that our ErrorMessage is not consistent on the client and server-side at the moment. There’s a good reason for this – if you’re developing and testing on a local machine, it’s a quick way to test that your client-side validation is actually working, especially if your machine is fast and you fail to notice any postbacks.

Refactor part 1 – make the error consistent

Now, in the spirit of what happens in actual development, we can start to refactor. First things first, let’s make our error message consistent. To do this, we will have to change our MustFlingSomethingValidator. We start by adding a constructor which we can use to call our base constructor passing in the error message we want to use:


public MustFlingSomethingValidator() : base("A monkey must fling something") {
        }

Now we change the ValidationResult IsValid method to return this error message by using the FormatErrorMessage method of ValidationAttribute that our class inherits from.

protected override ValidationResult IsValid(object value, ValidationContext validationContext)

        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
	}

Note that FormatErrorMessage takes a nice parameter that is the name of the field we are validating. See, the ValidationAttribute has a lovely little property that is ErrorMessageString. This is, by default “The field [object Object] is invalid.” When you call FormatErrorMessage and pass in the field you are validating, the “[object Object]” gets replaced, obviously, by the field name and you get a nice error message like “The field Fling Poo is invalid”. Still not the most descriptive error, but it forms the basis of a good validation error message. Of course our class has contructed itself with an error message of “A monkey must fling something”, so really, passing the field name is largely useless for our purposes. We could simply return with ErrorMessageString like this:

return new ValidationResult(ErrorMessageString);

However, I’m using FormatErrorMessage(validationContext.DisplayName) because it might be more usefull to you in the future.

A last little change to our ModelClientValidationRule, making the error message use the FormatErrorMessage method. :

public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)

    {
            ModelClientValidationRule mvr = new ModelClientValidationRule();
            mvr.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
            mvr.ValidationType = "mustflingsomething";

            return new[] {mvr};
        }

Refactor part 2 – abstract a class because that’s how everyone else does it

Now, since we are refactoring, I’ll point out that most resources online will refactor this bit of code:

ModelClientValidationRule mvr = new ModelClientValidationRule();
mvr.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
mvr.ValidationType = "mustflingsomething";

into its own class and then simply return a new instance of that class. Obviously you want to do this because it creates a nice set of abstractions to reuse in other validators and more importantly, it confuses the hell out of anyone else who has never had to do this before. You really want to create a rather nice little layer of obfuscation that makes very little sense on a small project because it’s the way it’s done in real big projects. Also, if you are the only one who understands the code, it puts you in a stronger negotiating position when it comes to your salary review. Seriously, this step is completely unnecessary for a small example app like this one, but it’s here because you’ll see it elsewhere and I don’t want you to be confused.

So, create a new class in your validation namespace. Call it MustFlingSomething and type the following in (remembering to include the System.Web.Mvc namespace up top – which I haven’t shown in the code below):

namespace ChunkyMonkey.Validation
{
    public class MustFlingSomething : ModelClientValidationRule
    {
        public MustFlingSomething(string errorMessage)
        {
            ErrorMessage = errorMessage;
            ValidationType = "mustflingsomething";
        }
    }
}

And now, back to your ModelClientValidationRule….changing it to use your newly created class:

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

Yep, that’s much clearer. Much more re-usable. Much more abstracted for the sake of it.

Soooooo, after all that, we have a reasonable good piece of code that will fire on both client and server side. All we need to do now is add in our validation logic.

Validation Logic – server-side

Open MustFlingSomethingValidator and change it to include some our validation logic. The validator is basically attached to each property on our model which is decorated with the data annotation. So, to properly validate, we need to create our validator and pass in the “other thing to fling” so we can check if both our current property *and* the other property are empty strings. So, the steps here are:

  • create a property in MustFlingSomethingValidator called OtherThingToFling
  • modify our constructor to accept the “other thing to fling”
  • write our logic in the IsValid method
public class MustFlingSomethingValidator:ValidationAttribute, IClientValidatable
{
    public string OtherThingToFling { get; set; }

    public MustFlingSomethingValidator(string otherThing) : base("A monkey must fling something") {
        OtherThingToFling = otherThing;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var otherThingToFlingObject = validationContext.ObjectInstance.GetType().GetProperty(OtherThingToFling);
        var otherThingToFlingValue = otherThingToFlingObject.GetValue(validationContext.ObjectInstance, null);
        if (value == null && otherThingToFlingValue == null)
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }
       return ValidationResult.Success;
    }
}

There you go – simple eh? Ptha. You feel like smashing up that waste wood yet? Go for it, I’ll wait right here. Sooooooo in our IsValid method we have to first get the actual otherThingToFling object from our validation context and then get the actual value of it. Cool. Go read the documentation for ValidationContext. Go on. You won’t really be any the wiser for it, so just cut and paste the above changing your variable names as required.

Awesome. Now we need to change our model – the data annotations need to pass in “OtherThingToFling”…..

    public class Monkey
{
    public int Id { get; set; }

    [MustFlingSomethingValidator("FlingToy")]
    public string FlingPoo { get; set; }
    [MustFlingSomethingValidator("FlingPoo")]
    public string FlingToy { get; set; }
}

Things are looking good. I suggest you disable your client side script and test out the server side logic. To do this, in your view, simply delete the included mustFlingSomething.js file.

Okay, re-include your javascript file and move on.

Validation Logic – client-side glue

Next we need to pass this “other thing” through to the client. Cue a lot of extra code – first in MustFlingSomething:

public class MustFlingSomething : ModelClientValidationRule
{
    public MustFlingSomething(string errorMessage, string otherThingToFling)
    {
        ErrorMessage = errorMessage;
        ValidationType = "mustflingsomething";
        ValidationParameters.Add("otherthingtofling", otherThingToFling);
    }
}

You can see that we’ve simply added a string to our constructor and called the ValidationParameters.Add method – if you remember, this is the class we instantiated which helped us pass values between the server-side and client-side code. It stands to reason then, that when we instatiate it in MustFlingSomethingValidator, we need to insert OtherThingToFling. So change the GetClientValidationRules of MustFlingSomethingValidator to this:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
MustFlingSomething(FormatErrorMessage(metadata.GetDisplayName()), OtherThingToFling) };
        }

Validation Logic – javascript

because the last thing we need to do is change it a little to allow the client side validation to run:

$(function () {
    jQuery.validator.addMethod('mustflingsomething',
    function (value, element, params) {
        var otherThingToFling = $('#' + params.otherthingtofling).val();
        if (value == '' && otherThingToFling == '') {
            return false;
        }
        return true;
    }, '');
    jQuery.validator.unobtrusive.adapters.add('mustflingsomething',['otherthingtofling'],
    function (options) {
        options.rules['mustflingsomething'] = {
            otherthingtofling: options.params.otherthingtofling
        };
        options.messages['mustflingsomething'] = options.message;
    });
} (jQuery));

Note several things – first we include a variable in our options.rules object – this will be passed into our custom jQuery validator extension method. Cool. Next in our actual extension method that we added, we access our new variable using a little jQuery – note the params object contains all the various bits and bobs you passed in through the options.rules object. Also keep in mind that the “value” variable is the value of the object that we have placed our data annotation against.

The javascript we wrote looks suspiciously like the server-side code we wrote in MustFlingSomethingValidator doesn’t it? Which brings inquiring minds to several questions –

  • Wouldn’t it be nice if we didn’t have to duplicate that code?
  • Why the heck did I jump through so many hoops to write a custom jQuery function based on obfuscation of variables when I could have written a jQuery function without passing it from the server-side through a bunch of objects that are going to confuse the hell out of the new guy who is going to support this code?
  • Isn’t that was a lot of code to simply compare to variables?
  • What exact advantage do all the intermediary glue classes like the ModelClientValidationRule and the unobtrusive javascript adapter give me since I have to write the actual validation logic code twice anyway?

Yes. Good questions. I have no answers.

Hopefully, you have a working solution at this point and you can go and customise it for your own project. If there are error up there, drop me a comment and I’ll correct them.

Go smash up that waste wood, grab a nice cup of coffee and look at some trees for a while to clear your head. Seriously, you deserve it.

One thought on “ASP.NET MVC3 and custom validation – a tale of ModelClientValidationRules, unobtrusive javascript, jQuery validation and poo”

  1. Outstanding and enjoyable to read. By expressing your own frustration you have clearly explained how much of a clusterxxxx this is. I’m happy to know that I’m not nuts. However, I don’t know of any better way MS could have done this.

Comments are closed.