20 July 2012

MVC Data Annotation Validators - The Solution

This "Problem - Solution" is a two series article . So this is the second part: The Solution.
The first part explains the problems facing the implementation of the MVC Data Annotation Validators in a Service Oriented Architecture (SOA) with clear separation of the layers. You can read it here

The solution I found is to use Fluent Validation framework with an Inversion of Control container (IoC) to instantiate my validators.

The Fluent Validation framework is a validation engine that can be used in several scenarios. In this specific solution, I will be focusing in the integration with MVC without using any attributes.
To use Fluent Validation with ASP.Net MVC I am going to use an Inversion of Control container to instantiate the validators.

The difference between an Inversion of Control (IoC) and any other kind of frameworks is that the control gets inverted. The objects in a application are controlled by the Inversion of Control Containers and the application is completely unaware of what the IoC does. A IoC container manages it's life-cycle, invoks methods and is fully autonomous form the application.

The solution implemented by Fluent Validation is to use a custom Validator Factory.
The process to implement can be described in the following steps:
1) Create a Validator Factory for FluentValidation that inherits from ValidatorFactoryBase. Override the CreateInstance method to call the IoC container that is responsible for instantiating the validators.
public class StructureMapValidatorFactory : ValidatorFactoryBase
{
    public override IValidator CreateInstance(Type validatorType)
    {
        return ObjectFactory.TryGetInstance(validatorType) as IValidator;
    }
}

I am going to use StructureMap as the IoC Container.
2) Create a StructureMap Controller Factory that inherits from the MVC DefaultControllerFactory.
public class StructureMapValidatorFactory : ValidatorFactoryBase
{
    public override IValidator CreateInstance(Type validatorType)
    {
        return ObjectFactory.TryGetInstance(validatorType) as IValidator;
    }
}

3) Register your validator types with StructureMap, using the FluentValidation AssemblyScanner. The AssemblyScanner automatically registers all of the validator classes in a particular assembly with a IoC container.
public class MyRegistry : StructureMap.Configuration.DSL.Registry
{
    public MyRegistry()
    {
        FluentValidation.AssemblyScanner.FindValidatorsInAssemblyContaining<CustomerValidator>()
            .ForEach(result =>
            {
                For(result.InterfaceType)
                    .Singleton()
                    .Use(result.ValidatorType);
            });

    }
}

4) Configure MVC to use FluentValidation MVC integration in Global.asax Application_Start
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    // Configure structuremap
    ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
    ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());

    // Configure FluentValidation to use StructureMap
    var factory = new StructureMapValidatorFactory();

    // Tell MVC to use FluentValidation for validation
    ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(factory));
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
}

5) Now the real fun can begin. The FluentValidation library can be used in MVC.
Let's create a customer validator class, where the First Name and Last Name are mandatory and we also want to set a custom error message.
Also perform the same validation on the email and guarantee that the email follows the basic rules.
The validation class must inherit from AbstractValidator.
The rules are defined using lambda expressions.
public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor(customer => customer.FirstName).NotEmpty().WithMessage("Please fill the first name.");
        RuleFor(customer => customer.LastName).NotEmpty().WithMessage("Please fill the last name.");
        RuleFor(customer => customer.EmailAddress).EmailAddress().WithMessage("Please fill a valid email address.")
                                                    .NotEmpty().WithMessage("Please fill the first email.");
    }

}

The FluentValidation has Built in Validators or you can define your custom validators.
You can read more about the fluent validation here
Structuremap download and documentation can be found here

No comments: