Lambda Validators Part 2 – Custom Validators

I previously posted about a small validation library that I wrote that uses lambda expressions for defining validation rules.

Over the last couple of days I’ve spent some time refactoring it in order to support custom validator classes which can be useful in keeping your validation logic nicely hidden and out of the way.

So imagine you have a set of validation rules like this:

var person = new Person();
 
var validator = Validator.For(person).Rules(
	rule => rule.For(p => p.Forename).NotNull().Lengh(1, 50),
	rule => rule.For(p => p.Id).NotEqual(0),
	rule => rule.For(p => p.Surname).NotNull().Lengh(1, 50)
);
 
bool success = validator.Validate();

Using a custom validator, the validation rules can now be moved into a separate class:

public class MyPersonValidator : AbstractValidator<Person> {
	public MyPersonValidator() {
		RuleFor(p => p.Forename).NotNull().Lengh(1, 50);
		RuleFor(p => p.Id).NotEqual(0);
		RuleFor(p => p.Surname).NotNull().Lengh(1, 50);
	}
}

…and now the code for doing the validation is greatly simplified:

var person = new Person();
var validator = Validator.For(person);
bool success = validator.Validate();

In order for this to work you will need to register the validator class with your favourite IoC container and then tell the ValidatorFactory class to use that IoC container to instantiate the validators. Typically you would do this in your application’s startup routine – Application_Start for an ASP.NET app.

protected void Application_Start(object sender, EventArgs e) {
	//Set up windsor
	IoC.Initialise(new WindsorContainer());
	//Register the validator with Windsor
	IoC.Container.Register(
		Component.For<IValidator<Person>>().ImplementedBy<MyPersonValidator>().LifeStyle.Transient;
	);
 
	//Tell the ValidatorFactory to use Windsor to look for validators:
	JS.Validation.ValidatorFactory.Initialise(type => IoC.TryResolve(type));
}

In the above example, the IoC class is a static wrapper around the Windsor container

How does it work?

When you call Validator.For(object), the ValidatorBuilder will ask the ValidatorFactory for an IValidator<T> (where T is the object being validated), and the delegate that you passed to ValidatorFactory.Initialise() is invoked with that type as its parameter.

I decided to use this approach as I didn’t really want to create a dependency on a particular IoC container.

The validation rules defined in the validation class are combined with any rules defined by calling Validator.For(object).Rules(), so you can mix and match validation rules as necessary.

Custom Property Validators

It is also possible to create custom property validators. For example, to create a custom validator that checks whether a property’s value is not equal to “Foo”, you might do something like this:

Validator.For(person).Rules(
	rule => rule.For(p => p.Surname).NotNull().Add(new NotEqualToFooValidator());
);

NotEqualToFooValidator would be defined like so:

public class NotEqualToFooValidator : PropertyValidatorBase {
	public override bool Validate(object instance, object value) {
		if("Foo".Equals(value)) {
			Error = string.Format("'{0}' must not be equal to 'Foo'.", FieldName);
			return false;
		}
		return true;
	}
}

This can also be wrapped in an extension method…

public static class ValidatorExtensions {
	public static ILambdaPropertyValidatorBuilderWithOptions<T, TProperty> CannotBeFoo<T, TProperty>(this ILambdaPropertyValidatorBuilder<T, TProperty> builder) {
		return builder.Add(new NotEqualToFooValidator());
	}
}

…which makes thing a little more fluent:

Validator.For(person).Rules(
	rule => rule.For(p => p.Surname).NotNull().CannotBeFoo();
);

For something this simple, you’d usually use the NotEqual validator (rule.For(x => x.Surname).NotEqual(“Foo”)), but you get the idea.

Any custom property validators can also be used in conjunction with the custom validator classes I mentioned above.

NotEqualPropertyValidator

I’ve also added NotEqualProperty validator that ensures the value of one property is not equal to the value of another:

Validator.For(person).Rules(
	rule => rule.For(p => p.Forename).NotEqual(p => p.Surname);
);

As usual, the code is in my svn repository.

HandleErrorAttribute and Remote Connections

Last week I upgraded my main application to MVC Preview 4. This morning I noticed that a number of users were receiving HTTP 500 error messages instead of being presented with a friendly error page.

It turns out the HandleErrorAttribute in Preview 4 is the culprit. Previously I’d been using MvcContrib’s RescueAttribute.

It appears that the HandleError attribute only works locally – when using a remote connection the server just displays the standard HTTP 500 message (this is under IIS7 using Integrated Pipeline).

I fired up the MVC source code and removed this line from HandleErrorAttribute.cs (line 85):

filterContext.HttpContext.Response.StatusCode = 500;

After doing this, the error pages are displayed correctly.

ASP.NET MVC Preview 4

I spent yesterday updating our largest application to MVC Preview 4. As part of the upgrade, I also made some tweaks to MvcContrib’s ConventionController (documentation not yet updated).

Here’s a summary of some of the things that have changed:

  • TempData finally works with out of process session state
  • Filters that implement IExceptionFilter can easily catch exceptions thrown by other filters
  • The RescueAttribute in MvcContrib is now an IExceptionFilter, so it can be used with classes that don’t inherit from ConventionController
  • Multiple filters can now have the same sort order. This makes it easy to define a ‘default’ order, and then have other filters execute before/after
  • TempData is now testable without needing to mock anything
  • All the ComponentController hackery is gone – RenderAction is a much nicer alternative
  • The bug that broke URL generation is fixed

Here’s what sucks with Preview 4:

  • SubDataItems still doesn’t seem to work properly
  • AuthorizeAttribute, OutputCacheAttribute and HandleError attribute are sealed and have no virtual methods
  • The new built in Ajax support doesn’t seem to be easily replaceable with alternative libraries