Site Meter
 
 

Monthly Archives: January 2009

xVal – a validation framework for ASP.NET MVC

Update: The version of xVal presented in this post (and in its downloadable demo project) has been superseded by a newer version with extra features. This post is still a good introduction to the concept, but to download the latest version of xVal please go to http://xval.codeplex.com. Thanks!

logo100pxI’ve written about validation in ASP.NET MVC more than once already. So have others (Stephen Walther, Emad Ibrahim). It’s an ongoing subject of discussion because the MVC Framework comes with great support for server-side validation (though it isn’t obvious if you come from a WebForms background), but at least as of version 1.0, it won’t ship with any built-in support for client-side validation.

I managed to get a few spare days this last week, so I decided to try and formalize lots of these ideas into something solid that you can actually use in real production code – something more than just another throwaway blog post. So here it is: xVal, an open-source project hosted on CodePlex.

What does it do?

xVal lets you link up your choice of server-side validation mechanism with your choice of client-side validation library. It guides you to fit them both into ASP.NET MVC conventions, so everything plays nicely with model binding and errors registered in ModelState.

image

The design goals are as follows:

  • Fit in with ASP.NET MVC conventions
  • Promotes a clear validation pattern that works whether or not the visitor has JavaScript enabled (graceful degradation)
  • Lets you plug in your choice of server-side validation framework or custom code. The current version includes rules providers for .NET 3.5’s built-in DataAnnotations attributes as well as Castle Validator attributes, but you can also write your own IRulesProvider to attach rules programmatically or using any other .NET validation framework.
  • Lets you plug in your choice of client-side validation library. The current version includes plugins for jQuery Validation as well as the native ASP.NET validation code (as used in WebForms), but you can use the standard JSON-based rules description format to write plugins for any other client-side validation library.
  • Supports internationalization, so you can vary the display of error messages by the current thread UI culture.
  • To be a high-quality product. xVal is built using TDD and comes with a solid set of unit tests. It uses xUnit and Moq for testing the server-side code, and Selenium RC for testing the client-side code.

The current version on Codeplex – version 0.5 – works with ASP.NET MVC Beta. It’s mostly solid and usable, though there are a few rough spots (e.g., a couple of inconsistencies to do with date formatting) that I’ll iron out soon. I also have plans to improve the internationalization somewhat to let you get error messages in other languages without having to specify them explicitly. Also, I’ll be adding support for DataAnnotations’ notion of “buddy classes” so that it works much better with the LINQ to SQL designer’s generated entity classes.

Quick tutorial

I don’t have time to document everything in xVal just yet. The basic usage pattern is supposed to be straightforward so hopefully the following tutorial won’t seem too clever or weird.

Let’s say you’ve got a model class defined as follows. Notice that we’re using attributes such as [Required], so you need to reference the System.ComponentModel.DataAnnotations.dll assembly:

public class Booking
{
    [Required] [StringLength(15)]
    public string ClientName { get; set; }
 
    [Range(1, 20)]
    public int NumberOfGuests { get; set; }
 
    [Required] [DataType(DataType.Date)]
    public DateTime ArrivalDate { get; set; }
}

You might add code to an ASP.NET MVC controller to create instances of this class:

public class HomeController : Controller
{
    [AcceptVerbs(HttpVerbs.Get)]
    public ViewResult CreateBooking()
    {
        return View();
    }
 
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult CreateBooking(Booking booking)
    {
        BookingManager.PlaceBooking(booking);
        return RedirectToAction("Completed");
    }
 
    public ViewResult Completed()
    {
        return View(); // Displays "Thanks for booking"
    }
}

This controller references some code in the domain tier that actually places bookings:

public static class BookingManager
{
    public static void PlaceBooking(Booking booking)
    {
        // Todo: save to database or whatever
    }
}

Of course, you’d also need a view for CreateBooking, containing the following markup:

<h1>Place a booking</h1>
<% using(Html.BeginForm()) { %>
    <div>
        Your name: <%= Html.TextBox("booking.ClientName") %>
        <%= Html.ValidationMessage("booking.ClientName") %>
    </div>
    <div>
        Number of guests: <%= Html.TextBox("booking.NumberOfGuests")%>
        <%= Html.ValidationMessage("booking.NumberOfGuests")%>
    </div>
    <div>
        Expected arrival date: <%= Html.TextBox("booking.ArrivalDate")%>
        <%= Html.ValidationMessage("booking.ArrivalDate")%>
    </div>
 
    <input type="submit" />
<% } %>

This is all fine, but so far, no validation is enforced.

Enforcing server-side validation

As I’ve discussed before, validation rules should go in your domain model, because the role of your model is to represent the workings of your business. It ensures that the rules will be enforced consistently, regardless of whether the UI coder remembers them or not.

At this point, reference xVal in both your domain model project and in your ASP.NET MVC project.

Let’s update BookingManager.PlaceBooking():

public static class BookingManager
{
    public static void PlaceBooking(Booking booking)
    {
        var errors = DataAnnotationsValidationRunner.GetErrors(booking);
        if (errors.Any())
            throw new RulesException(errors);
 
        // Business rule: Can't place bookings on Sundays
        if(booking.ArrivalDate.DayOfWeek == DayOfWeek.Sunday)
            throw new RulesException("ArrivalDate", "Bookings are not permitted on Sundays", booking);
 
        // Todo: save to database or whatever
    }
}

Now, the model tier enforces its own validity by refusing to place bookings that don’t meet all validation and business rules. It throws a special type of exception, RulesException (defined in the xVal assembly) that passes structured error information back to the caller.

Aside: The best way of thinking about business rules, in my opinion, is that you’re validating an operation (i.e., a unit of work). For example, “documents can only be published when they have been approved by someone in the Moderator role” – this is an act of validation on the operation of publishing.

The DataAnnotations attributes don’t come with a validation runner. Here’s a very simple (and slightly naive one) referenced by the previous code block:

internal static class DataAnnotationsValidationRunner
{
    public static IEnumerable<errorInfo> GetErrors(object instance)
    {
        return from prop in TypeDescriptor.GetProperties(instance).Cast<propertyDescriptor>()
               from attribute in prop.Attributes.OfType<validationAttribute>()
               where !attribute.IsValid(prop.GetValue(instance))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
    }
}

Finally, let’s fit in with ASP.NET MVC conventions by transferring any detected errors to ModelState:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateBooking(Booking booking)
{
    try {
        BookingManager.PlaceBooking(booking);
    }
    catch(RulesException ex) {
        ex.AddModelStateErrors(ModelState, "booking");
    }
 
    return ModelState.IsValid ? RedirectToAction("Completed")
                              : (ActionResult) View();
}

Now, because we already have Html.ValidationMessage() helpers in the view, our error messages will appear:

image

This is the essence of server-side validation. You validate requested operations, both in terms of model properties and arbitrary business rules expressed in C# code, and if anything is bad, you put the error information into ModelState and then re-render the view.

So far, we’ve only used xVal as a way of transferring error information from the model tier to the UI tier. The clever bit happens when we want to add client-side validation.

Applying client-side validation

xVal comes with support for native ASP.NET client-side validation and jQuery Validation. Let’s use jQuery Validation. Add jquery.validate.js (which you can download here) and xVal.jquery.validate.js (included in xVal) to your /Scripts folder, then add references in your master page:

<head>
    <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery-1.2.6.js")%>"></script>
    <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery.validate.js")%>"></script>
    <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/xVal.jquery.validate.js")%>"></script>
</head>

Also, import xVal’s HTML helpers by adding the following line to your web.config’s <namespaces> node:

<system.web>
  <pages>
     <namespaces>
        <!-- leave rest as-is -->
        <add namespace="xVal.Html"/>
    </namespaces>
  </pages>
</system.web>

Now, all you have to do is tell xVal that you want to apply client-side validation to the controls with IDs prefixed by “booking.”. Add the following line to your view:

<%= Html.ClientSideValidation<booking>("booking") %>

That’s it! jQuery Validation will now receive and enforce the rules defined by attributes.

image

Of course, if someone has JavaScript turned off, they’ll still be subject to the server-side validation enforced in the model tier. Also, the business rule about not allowing bookings on Sundays will still be enforced on the server either way.

Changing client-side validation libraries

If you decide you don’t like jQuery Validation, it’s trivial to switch to a different client-side library as long as you have an xVal plugin for it. For example, to switch to native ASP.NET validation, just remove the reference to xVal.jquery.validate.js (and the other jQuery references if you want), and reference the following instead:

<script type="text/javascript" src="<%= Page.ClientScript.GetWebResourceUrl(typeof(System.Web.UI.Page), "WebForms.js") %>"></script>
<script type="text/javascript" src="<%= Page.ClientScript.GetWebResourceUrl(typeof(System.Web.UI.Page), "WebUIValidation.js") %>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/xVal.AspNetNative.js")%>"></script>

Job done! You’re now using the native ASP.NET client-side validation libraries (as used by WebForms) and don’t need to use jQuery.

Download the finished demo project

What next

Over the next few weeks, I’ll try to find time to document the following features:

  • How to add custom validation logic that works both on the server and on the client
  • How to supply validation rules programatically or explicitly (i.e., if attributes on properties don’t meet your needs, or if you don’t like model-based validation)
  • How to internationalize your validation messages
  • How to write an IRulesProvider or a plugin for a client-side validation library
  • How to control the placement of validation messages
  • How to reuse a single rules configuration across multiple groups of form controls (e.g., when editing a list)

Comments and questions are appreciated, as well as offers to contribute to the xVal project! (For example, you could write a provider or plugin for a different server-side or client-side validation toolkit.)

In case it wasn’t obvious, xVal is hosted on CodePlex at http://xval.codeplex.com/. It’s open source, under the MS-PL license.

Drag-and-drop sorting for our ASP.NET MVC list editor (in one line of code)

So you can use ASP.NET MVC’s model binding conventions to implement a list editor where the user can dynamically add and remove items. Great! But how can you let the user control the order of the items? For example, when editing a list of actors in movie, the user might want to move the most famous actor up to the top of the list.

The natural UI metaphor here is drag-and-drop (not any nasty little “move up/down” arrows). To try the desired behavior for yourself, check out the following updated live demo:

Try it yourself (launch live demo) Download the source code

Implementation

Drag-and-drop used to be difficult, because every browser exposes a different API for it, but fortunately the jQuery UI project makes it much easier. What really surprised and delighted me is that jQuery UI’s Sortable component is so neatly compatible with the dynamic list editor from my previous blog post that we can implement the whole sorting business in just one single line of code!

Starting from the list editor demo from the previous blog post, all I needed to add was the following:

<script type="text/javascript">
    $(function() {
        $("#items").sortable({axis: "y"});
    });
</script>

This tells jQuery UI to get the elements inside the <div> with ID “items”, and make them sortable relative to one another. When the MVC Framework’s model binding facility later parses the incoming data to a collection, your action method will receive the data items in whatever order the user has sorted them. Job done!

Of course, to make this work, you also need to reference jQuery and jQuery UI, as follows:

  1. Go to the jQuery UI website and download a personalized version that includes the Sortable component. Put this in your ASP.NET MVC application’s /Scripts folder
  2. In your master page’s <head> section, reference both jQuery and your jQuery UI file:
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery-1.2.6.min.js") %>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery-ui-personalized-1.6rc4.min.js") %>"></script>