7 April 2009

Validate Data for Your Application with ASP.NET MVC, xVal, Castle and a Custom Binder

 

There are several stages at which you can, and possibly should, validate data inputted to your application.  This post deals with server-side validation of user input before it reaches the controller.  It’s arguably not the controller’s responsibility to validate user input, and it’s nice to have a clean controller that can focus on its responsibility.  In this solution we tap into the ASP.NET MVC Model Binder framework so our controllers can count on the model having been validated before any of the action methods are called.

xVal is a nice validation framework written by Steve Sanderson.  It can work with a bunch of server-side and client-side validation products.  I this case I’m going to use the server-side Castle Validator* (just because I like it).

* Link removed as it’s now dead 🙁

Once you have downloaded and referenced xVal and Castle Validator, you can decorate your model properties with attributes like so:

using System;
using Castle.Components.Validator;
namespace Wow.MvcApplication.Models
{
public class Person
{
[ValidateNonEmpty]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[ValidateNonEmpty]
public string LastName { get; set; }
[ValidateDate]
public DateTime Dob { get; set; }
}
}

Now we implement the validation runner that wraps up the Castle runner and returns validation data in xVal form:

using System.Collections.Generic;
using System.Linq;
using Castle.Components.Validator;
using xVal.ServerSide;
namespace SomeMvcApplication
{
public class CastleValidationRunner
{
private static readonly CachedValidationRegistry registry = new CachedValidationRegistry();
public static IList<ErrorInfo> GetErrors(object instance)
{
var result = new List<ErrorInfo>();
var runner = new ValidatorRunner(registry);
if (instance != null && !runner.IsValid(instance))
{
var errorSummary = runner.GetErrorSummary(instance);
var errorInfos = errorSummary.InvalidProperties.SelectMany(
prop => errorSummary.GetErrorsForProperty(prop),
(prop, err) => new ErrorInfo(prop, err));
result.AddRange(errorInfos);
}
return result;
}
}
}

Create a Model Binder that uses the new validation runner to populate ModelState:

using System.Web.Mvc;
namespace SomeMvcApplication
{
public class ValidatingModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext);
var errors = CastleValidationRunner.GetErrors(model);
foreach (var error in errors)
{
var propertyName = error.PropertyName;
var name = bindingContext.ModelName + "." + propertyName;
bindingContext.ModelState.AddModelError(name, error.ErrorMessage);
}
return model;
}
}
}

Now hook up our new Model Binder in Global.ascx.cs:

protected void Application_Start()
{
ModelBinders.Binders.DefaultBinder = new ValidatingModelBinder();
}

And we are done.  Controllers can now count on validated models like so:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Person model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// In real life, map the model to domain, then save
}

Search

Categories

Archives

Subscribe to Email Updates

Subscribe
 
  • beton

    Good read. Very simple and clean approach.

    What do you think about IValidatableObject interface?

We are a digital transformation consultancy. We help our clients succeed.

View Services