xVal – a validation framework for ASP.NET MVC
I’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.
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:
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
<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.
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.