Lambda Validators Part 2 – Custom Validators
July 26, 2008 – 13:28
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.
2 Responses
Very nice, but it still bothers me that you’re creating validation rules outside of your domain entity. I’d much rather see the validation rules on property setters and have them raise an exception when they fail. I’ve used this approach with a custom form binder and it works well. Of course you have to make sure your ORM doesn’t set properties via property setters, but most have this functionality built in.
My personal preference is to keep my entities as dumb as possible and have the validators in separate classes.
I’m also not sure I like the idea of using exceptions for validation…I prefer to reserve them for something truly ‘exceptional’
How do you deal with catching multiple validation errors with the exception approach?