Client & Server Side Validation in ASP.NET MVC

[Update: The code is open-sourced at http://code.google.com/p/mvcvalidation/]

The asp.net mvc codeplex preview 5 version just came out a few days ago and it includes a lot nice enhancements.  For the best introduction read Scott’s post

Some of my favorite changes happened in validation.  Incidentally, I was already working on a post to talk about doing client and server side validation the easy way.  The inspiration came after reading Steve Sanderson’s post and watching his screencast on how he created a model-based client-side validation – very cool.

I want a simple way to perform client and server side validation for the application I am creating for my book and my objectives are simple (yet there is no easy way to do it):

  1. Have a model-based validation mechanism
  2. Have the least amount of repetition (DRY)
  3. Client side validation using jQuery
  4. Server side validation

First, I will show you how it works then I will explain how I got there.  Here is my model class for a user

   1: public class User
   2: {
   3:     public virtual string UserName { get; set; }
   4:     public virtual string Email { get; set; }
   5:     public virtual string Password { get; set; }
   6:     public virtual Guid ProviderId { get; set; }
   7: }

To add validation to it, I simply decorate it with the appropriate validation attributes.  It ends up looking like this:

public class User
{
    [ValidateNonEmpty("Username is required")]
    public virtual string UserName { get; set; }

    [ValidateNonEmpty("Email is required")]
    [ValidateEmail("Invalid email address")]
    public virtual string Email { get; set; }

    [ValidateNonEmpty("Password is required")]
    [ValidateLength(4,50,"Password should be at least 4 characters long")]
    public virtual string Password { get; set; }

    public virtual Guid ProviderId { get; set; }
}

I then generate the client script by adding 1 line of code to my view

<%=Html.ClientSideValidation("formCreateList", ViewData.Model) %>

On the server side, I simply make a call to a validation method from my controller

[Authorize]
[AcceptVerbs("POST"), ActionName("Create")]
public ActionResult Save(User newUser)
{
    AppHelper.ValidateModel(ViewData.ModelState, newUser);
    //the rest of the action code
}

If there are any validation errors on the server, I display the errors by using the framework helper method ValidationSummary.  All I have to do is add this 1 line to the view

<%=Html.ValidationSummary() %>

JavaScript

That’s all there is to it.  Now let’s talk about how it works.  The first challenge was generating the JavaScript to validate on the client. 

I decided to use the excellent jQuery Validation plugin.  The plugin gives me 2 ways to validate my form.  The first method uses classes on the the fields to be validated, for example a required email field would look like this

<input id="email" name="email" size="25"  class="required email" />

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

The second method uses rules that are defined in JavaScript and look like this

<script type="text/javascript"> 
$("#signupForm").validate({
        rules: {
            email: {
                required: true,
                email: true
            }
        },
        messages: {
            email: "Please enter a valid email address"
        }
    });
</script>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Check out the plugin documenation and demos to understand how it all works.

Since I was generating the script on the server and I wanted to make only one call, I went with the second option.

The script generator simply loops through all the model properties and their attributes and generate the appropriate script.  Afterthought: Now that I think about it, I could have easily generated a script to add classes to the elements using jQuery’s addClass method…  Oh well, maybe someone else will be kind enough to do it.

Castle Validator

I used the Castle Validator Component as my validation framework.  That’s where all the validation attributes you see above come from, but you can also create your own validators.  I then created a class called JQueryValidationProvider that implements IBrowserValidationGenerator interface in the Castle.Components.Validator namespace.  I haven’t implemented the entire interface and only have a couple of validations working (I will update the source code when it is all done).  Here is a couple of methods that I implemented.

public void SetEmail(string target, string violationMessage)
{
     Rules.Add(new ValidationRule(Validate.Email, violationMessage));
}
public void SetAsRequired(string target, string violationMessage)
{
    Rules.Add(new ValidationRule(Validate.Required, violationMessage));
}

As you can see, all I am really doing is just adding the validation rule to List of ValidationRules.  ValidationRule is a straight forward class and looks like this:

internal class ValidationRule
{
    public ValidationRule(Validate validate) : this(validate, null) { }
    public ValidationRule(Validate validate, string failureMessage)
        : this(validate, failureMessage, null) { }
    public ValidationRule(Validate validate, string failureMessage, object value)
    {
        Validate = validate;
        Value = value;
        if (failureMessage != null)
        {
            ErrorMessage = failureMessage;
        }
        else
        {
            //setup default error messages
            switch (Validate)
            {
                case Validate.Required:
                    ErrorMessage = "This field is required";
                    break;
                default:
                    ErrorMessage = "Field error";
                    break;
            }
        }

    }
    public readonly object Value;
    public readonly Validate Validate;
    public readonly string ErrorMessage;
}

The helper ClientSideValidation method simply loops through the collection of rules and generate the appropriate JavaScript.  Here it is

public static string ClientSideValidation(this HtmlHelper htmlHelper,
    string formName,
    object modelToValidate)
{
    var results = new StringBuilder();
    var rules = new StringBuilder();
    var messages = new StringBuilder();
    results.AppendFormat("$("#{0}").validate({1}", formName, "{");
    results.AppendLine();
    rules.AppendFormat("rules: {0}", "{");
    messages.AppendFormat("messages: {0}", "{");

    var props = TypeDescriptor.GetProperties(modelToValidate.GetType());

    var propCounter = 0;
    foreach (PropertyDescriptor prop in props)
    {
        var generator = new JQueryValidationProvider();
        foreach (var attrib in prop.Attributes.OfType<AbstractValidationAttribute>())
        {
            var v = attrib.Build();
            v.ErrorMessage = v.ErrorMessage ?? "*";
            v.Initialize(new CachedValidationRegistry(), null);
            v.ApplyBrowserValidation(null, InputElementType.Undefined,
                                     generator, null, null);
        }

        if (generator.Rules.Count > 0)
        {
            if (propCounter > 0)
            {
                rules.Append(",");
                messages.Append(",");
            }
            rules.AppendLine();
            messages.AppendLine();
            rules.AppendFormat("{0}: {1}", prop.Name.ToLower(), "{");
            messages.AppendFormat("{0}: {1}", prop.Name.ToLower(), "{");
            rules.AppendLine();
            messages.AppendLine();

            for (var i = 0; i < generator.Rules.Count; i++)
            {
                var rule = generator.Rules[i];
                rules.AppendFormat("{0}", rule.GetRuleString());
                messages.AppendFormat("{0}", rule.GetMessageString());

                if (i < generator.Rules.Count - 1)
                {
                    rules.Append(",");
                    messages.Append(",");
                }
                else
                {
                    rules.Append("}");
                    messages.Append("}");
                }
                rules.AppendLine();
                messages.AppendLine();
            }

            propCounter++;
        }
    }

    rules.Append("},");
    messages.Append("}");
    rules.AppendLine();
    messages.AppendLine();
    results.Append(rules.ToString());
    results.Append(messages.ToString());
    results.Append("});");

    return string.Format("<script type='text/javascript'><!--{1}{0}{1}--></script>", 
                            results.ToString(), Environment.NewLine);
}

Changes

Halfway through this post, I read Steve Sanderson’s new post and Stephen Walther’s new post on validation, I made some changes to the code.  I agree with their point that the controller shouldn’t be responsible for initiating the validation and it should be done at a lower layer.  I refactored the code and my controller now looks like this

[AcceptVerbs("POST")]
public ActionResult Create(User user)
{
    try
    {
        user = new User();
        UpdateModel(user, new[] { "name", "email" });
        var service = new UserService();
        service.Save(user);
    }
    catch (RuleViolationException ex)
    {
        MvcValidator.PopulateModelStateWithErrors(user, ViewData.ModelState, ex);
    }
    return View(user);
}

 

The validation gets initiated at the service (business logic) layer and if there is a rule violation an exception is thrown.  I then take the exception and pass it to a helper method that populates the model state. Note: I could probably rewrite this so that the ModelState population happens before the exception throwing which will eliminate the call to the population method.

My view looks like this

<%=Html.ValidationSummary() %>
<form id="formCreate" name="formCreate" 
        action='<%=Url.Action("Create")%>' method="post">
    <label for="name">Name</label>
    <input type="text" id="name" name="name" />
    <br />
    <label for="email">Email</label>
    <input type="text" id="email" name="email" />
    <br />
    <input type="submit" value="Submit" />
</form>
<%= Html.ClientSideValidation("formCreate", ViewData.Model) %>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

Now if I try to submit the form, the client script validation runs and I get these errors

clip_image001

If you get rid of client validation and only use server validation

to submit the form, the form would look like this

clip_image001[4]

Finally, here is the client JavaScript generated in the view:

<script type='text/javascript'><!--
    $("#formCreate").validate({
        rules: {
            name: {
                required: true,
                minlength: "3",
                maxlength: "25"
            },
            email: {
                email: true,
                required: true
            }
        },
        messages: {
            name: {
                required: "Username is required",
                minlength: "Username should be between 3 and 25 characters long",
                maxlength: "Username should be between 3 and 25 characters long"
            },
            email: {
                email: "Invalid email address",
                required: "Email is required"
            }
        }
    });
--></script> 

 

Pros & Cons

Here is what I think the pros of this design are:

  1. I love using model attributes to define validation rules
  2. I like that I only need 2 lines in my view
  3. I like that I don’t have to do anything in the controller other than catch an exception and call a helper method

The one thing I don’t like about this design is that it feels like there is a lot of dependencies.  I might need to refactor the code and move things around.  Here is my dependency diagram

image

Credit

This code was possible because of other people’s work:

Stephen Walther – link

Steve Sanderson – link and link 2

Hamilton Verissimo – link

Rick Strahl – link

Scott Guthrie – link

Source Code

Click here to download the rough-quality source code.

Let me hear what you think of this design.  What’s wrong with it?  What’s right?  Should I put this up on codeplex?