• Anthony Stevens

ASP.NET MVC 3 Unobtrusive Javascript Validation With Custom Validators

Uncategorized

I spent some time this weekend learning about the new Unobtrusive Javascript Validation mechanism that ships with ASP.NET MVC 3 and, after a bit of hair pulling, got it to work properly with custom validators.  This post will summarize the steps necesssary.

  1. Include the correct JavaScript files
  2. Create your custom validator
  3. Tie the custom validator to the Model
  4. Set up validation on the field in your View
  5. Intercept the form post to force validation
  6. Create your custom client validation scripts

Overview

The point of unobtrusive Javascript validation is to seamlessly apply validation on form submit events, AND allow you to combine both server-side and client-side validation.  Client-side validation alone is almost worthless, because if you turn off Javascript in your browser you can bypass the validation scripts.  Server-side validation is (more) secure, but typically requires a round-trip and leaves some nice UX options off the table.

So, you need both.  In this example, I’ll be showing a custom Email validator that runs on the server side and has a client-side counterpart. (I know that there is already a built-in “email” validator available, but this is just for illustration).

1. Include the correct Javascript files.  I have the following in my master layout view:

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript">
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript">

To tell the truth, I’m not yet sure what the modernizr Javascript file is for – it may not be necessary.

2. Create your custom validator

In order for your custom validator to be able to function both on the server side and client side, you need to inherit from bothIValidationAttribute and IClientValidatable.  Here’s my class, which lives in my MVC 3 web project (NOT a library project):

public class ValidEmailAttribute : ValidationAttribute, IClientValidatable
    {
        public override bool IsValid(object value)
        {
            var isValid = false;
            
            string email = Convert.ToString(value);
            if ((String.IsNullOrWhiteSpace(email))
                || (email.Contains("@")))
            {
                isValid = true;
            }
            return isValid;
        }

        public override string FormatErrorMessage(string name)
        {
            return "The " + name + " field contains an invalid email address.";
        }

        public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            yield return new ModelClientValidationRule
            {
                ErrorMessage = FormatErrorMessage(metadata.DisplayName),
                ValidationType = "validemail"
            };
        }        
    }

3. Tie your custom validator to your model This step is easy. All you do is decorate the property you want to validate with an attribute of type ValidEmailAttribute, as follows:

[ValidEmail(ErrorMessage="Please enter a valid email address.")]
[DisplayName("Email")]
public string Email { get; set; }

4. Set up validation on the field in your View This step involves using the HtmlHelper .ValidationMessageFor() to show the validation message. You could also use the ValidationSummary helper, and I’m not yet clear on my preferred UX for validation errors. To each her own.

@Html.TextBoxFor(m=>m.Email)
@Html.ValidationMessageFor(m=>m.Email)

5. Intercept the form post to force validation This one was a little tricky. I wanted to do an AJAX post to a controller method, and MVC 3 provides an Ajax.BeginForm() method that appears to do what I want, but I’m a little old school and wanted to separate out the plain-old HTML

element from any AJAX-y stuff. So, I used jQuery to intercept the form submit, and in the process added a call to .valid() to invoke the jQuery client-side validation. My form declaration (using Razor):

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id="interestform" } )) { 

My submit handler:

$('#interestform').submit(function () {
    if ($(this).valid()) {
	$.ajax({
	    url: this.action,
	    type: this.method,
	    data: $(this).serialize(),
	    success: function (result) {
                $('#interestcontainer').html(result);
	    },
	    error: function (result) {
		alert(result);
	    }
    });
}
return false;
});

You’ll see the if ($this).valid()) call on the second line, which is key. Also of interest is the data: $(this).serialize(), which uses jQuery’s built-in serialize() function to take the form data and package it up in a form suitable for an AJAX call. 6) Create your own custom client validation scripts This step was hairy and took the most time to figure out. What happens to this point is that the form posts asynchronously even if the e-mail address is invalid. This told me that the client validation part wasn’t hooked up. This sort of makes sense, because how would the custom validator class know how to emit Javascript to do that custom validation? I did some Google research and discovered through trial and error that there are two things you need to do: a) Create a custom Javascript method that does your client validation on input values. In my case, that function looks like this:

jQuery.validator.addMethod("validemail", function (value, element, param) {
	var emailPattern = /^[a-zA-Z0-9._-]+@@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
	return emailPattern.test(value);
});

We make a call to jQuery.validator.addMethod to add our custom method, naming it "validemail". I think that the name MUST MATCH the name of the validation type value you sent back in the GetClientValidationRules method in your custom validation class, because the view emits special HTML5 attribute markup that ties that name to the validation class. The signature is interesting, and important, because you can create pretty complex validators that take parameters, do comparisons against other elements, etc. For now all we are looking at is the value parameter. b) Add your validator to the list of unobtrusive validators This call is simple:

jQuery.validator.unobtrusive.adapters.addBool("validemail");

We’re just telling jQuery to call our just-created "validemail" function when it does its validation (which, you’ll remember, we forced in our form submit handler. That’s it! You’re done! But there are some gotchas. First, don’t put your step 6 scripts inside a jquery document ready function. I was doing this at first, and it didn’t work:

$(function() {
jQuery.validator.addMethod("validemail", function (value, element, param) {
			var emailPattern = /^[a-zA-Z0-9._-]+@@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
			return emailPattern.test(value);
		});
jQuery.validator.unobtrusive.adapters.addBool("validemail");
});

I’m not exactly clear why this didn’t work, but take them out of the document.ready() function and you’ll be good to go.

Second, I recommend using the un-minified versions of the scripts during development, and stepping through the code using Firefox and Firebug. That helped a lot to tell what was going on. In my case, the server validation was fine, but the client validation wasn’t happening. Commenting out the actual AJAX call helped prevent any posts and focused my attention on the jQuery validation and unobtrusive libraries. Use breakpoints in Firebug. If you don’t know how, find out now.

Third, be aware that a lot of information out there is contradictory and/or based on earlier versions of MVC. In fact, treat this post with a grain of salt! Use your head and try to understand your code as it is working in front of your eyes, and not as some Stack Overflow post says it should work (although I <3 Stack Overflow, don’t get me wrong). Good luck!

1 Comment

1 Comment

  1. Ann Lewkowicz  •  Dec 15, 2011 @9:29 am

    Great post! I agree about the difficulty of finding good, still relevant information about validation with MVC 3! And thank you in particular for mentioning that the client-side validator setup can’t be inside a $().ready function. That solved a problem I’d already devoted serious time to.