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.