UPDATE 3: Exception throwing for business rules is not a good practice, making this post almost useless.
UPDATE 2: Did you notice the duplication in event handling?
UPDATE 1: Jeremy Skinner was kind enough to respond to my feature request, please look at the first block. Thanks Jeremy!
I have been trying to implement validation for BlogSharp. There are several validation frameworks including Castle Validator but it didn’t feel good to put attributes on the entity itself. I wondered if there is a 3.5 style of doing it and thankfully Jeremy Skinner provided a good framework that uses Lambda Expressions which is called Fluent Validation. Its syntax is very similar to the that of Fluent NHibernate.
public class PostCommentValidator : ValidatorBase<PostComment>
{
public PostCommentValidator()
{
RuleFor(x => x.Comment).NotEmpty();
RuleFor(x => x.Email).NotEmpty();
RuleFor(x => x.Email).EmailAddress(); RuleFor(x => x.Email).NotEmpty().And().EmailAddress();
RuleFor(x => x.Web).Url().When(x=>!string.IsNullOrEmpty(x.Web));
}
}
It is also easy to extend its validation capacities. All you need to do is to implement IPropertyValidator<T>. For my case, I needed to have Url validation and it was enough to inherit from RegularExpressionValidator.
public class UrlValidationRule<T>:RegularExpressionValidator<T>
{
public UrlValidationRule():base(@"URLRegularExpressionHere")
{
}
}
You may also want to create an extension method in order to have a uniform syntax.
public static class UrlValidationExtension
{
public static IRuleBuilderOptions<T, string> Url<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.SetValidator(new UrlValidationRule<T>());
}
}
There are some additions that i made in ValidatorBase<T>. Currently it returns ValidationResult, which has all the information about the validation process. I made it throw Exception in order not to deal with layering.
Nothing very special up to this point.
What I wanted to tell in detail today is how to implement validation using Db4o, Castle and Fluent Validation stack. Even though I was to do the validation at Service level, I decided that it may be better to have it at persistence level.
Db4o has events (did I mention that I like events?) that take place after and before various operations including Activate, Create, Update, Delete, Commit. The best place to implement this validation is to use the events Creating and Updating which are raised_before_ the updated/added object is stored back in Db4o. I needed a way to integrate them with my home made Db4o Facility that was inspired by Castle NHibernate Integration. I created an interface like the one below
public interface IDb4oInitializationHandler
{
void HandleObjectContainerCreated(IExtObjectContainer extObjectContainer);
}
Implementors will be called just after the IObjectContainer so that we will have the opportunity to wire up our events. Currently you can’t specify a specific InitializationHandler for a specific container, but I plan to implement it soon.
public void HandleObjectContainerCreated(IExtObjectContainer extObjectContainer)
{
var factory = EventRegistryFactory.ForObjectContainer(extObjectContainer);
factory.Creating += ValidationHandler;
factory.Updating += ValidationHandler;
}protected void ValidationHandler(object sender, CancellableObjectEventArgs args)
{
try
{
ValidateObject(args.Object);
}
catch(ValidationException ex)
{
args.Cancel();
throw ex;
}
}
and in the handlers of the events, I check if the stored object has validator associated with it and then validate.
protected virtual void ValidateObject<T>(T obj)
{
var type = obj.GetType();
var validatorType=typeof (IValidatorBase<>).MakeGenericType(type);
if(container.HasComponent(validatorType))
{
var validator = container.Resolve(validatorType) as IValidatorBase;
validator.ValidateAndThrowException(obj);
}
}
The exception is caught at the Controller, then is passed to the ModelState.
try
{
postService.AddComment(comment);
}
catch(ValidationException vex)
{
this.ModelState.AddValidationExceptionToModel("comment",vex);
}
AddValidationExceptionToModel is an extension method
public static void AddValidationExceptionToModel(this ModelStateDictionary model, string prefix,ValidationException exception)
{
var errors=exception.Errors;
foreach (var error in errors)
{
model.AddModelError(string.Format("{0}.{1}",prefix,error.PropertyName), error.Message);
}
}
The result is the following:
It is as easy as this, thanks to the extensibility of Db4o and Fluent Validation frameworks.
If you like it, don't forget to kick and/or shout it
50a29937-8fc7-484f-a041-5f6607c95495|0|.0
db4o, mvc, validation