In The craft
Validation in MVC 3 using FluentValidation
If you have not already watched Jimmy Bogard Putting your controllers on a diet go watch it now. The following code will not make any sense to you if you don’t.
Jimmy Bogard nicely demonstrates how to set up your MVC application with separation of concerns. In this case views that Query, are Forms, and perform validation. I took his approach and ran with it.
In this post I am only looking at the FormAction (watch the above video)
My implementation:
public class FormActionResult : ActionResult
{
public ViewResult Failure { get; private set; }
public ActionResult Success { get; private set; }
public T Form { get; private set; }
public FormActionResult(T form, ActionResult success, ViewResult failure)
{
Form = form;
Success = success;
Failure = failure;
}
private void ValidateModel(ControllerContext context)
{
object model = context.Controller.ViewData.Model;
Type genericType = typeof(AbstractValidator);
Type type = genericType.MakeGenericType(model.GetType());
//Structure Map container
var instance = DependencyResolver.Current.GetService(type);
if (instance != null)
{
ValidationResult validationResult = ((IValidator) instance).Validate(model);
validationResult.AddToModelState(context.Controller.ViewData.ModelState, null);
}
}
public override void ExecuteResult(ControllerContext context)
{
ValidateModel(context);
if (!context.Controller.ViewData.ModelState.IsValid)
{
Failure.ExecuteResult(context);
}
else
{
ExecuteHandler(context);
}
}
private void ExecuteHandler(ControllerContext context)
{
var handler = DependencyResolver.Current.GetService<IFormHandler>();
handler.Controller = context.Controller;
try
{
handler.Handle(Form);
Success.ExecuteResult(context);
}
catch (Exception exception)
{
Error error = new Error(exception);
ErrorLog.GetDefault(System.Web.HttpContext.Current).Log(error);
if (!handler.Controller.ViewData.ContainsKey(Constants.MessageKey))
{
handler.Controller.ViewData[Constants.MessageKey] = "An unexpected error has occured. Please try again.";
}
Failure.ExecuteResult(context);
}
}
}
At first it’s a bit overwhelming. Without knowing the context, one might not understand what’s going on. I’ll explain the code at a high level. In a nutshell this is a derived ActionResult class. The only method that is overridden from ActionResult is ExecutingContext. Everything else is specific to this class.
public override void ExecuteResult(ControllerContext context)
{
ValidateModel(context);
if (!context.Controller.ViewData.ModelState.IsValid)
{
Failure.ExecuteResult(context);
}
else
{
ExecuteHandler(context);
}
}
You’ll notice the first line is in this method is ValidateModel(context);. This is where all the yummy goodness happens.
private void ValidateModel(ControllerContext context)
{
object model = context.Controller.ViewData.Model;
Type genericType = typeof(AbstractValidator);
Type type = genericType.MakeGenericType(model.GetType());
//Structure Map container
var instance = DependencyResolver.Current.GetService(type);
if (instance != null)
{
ValidationResult validationResult = ((IValidator) instance).Validate(model);
validationResult.AddToModelState(context.Controller.ViewData.ModelState, null);
}
}
ValidateModel takes the incoming context and validates the model. The beauty about this entire process is first, it does not valid the model if a validator can not be found. Second, the validators are discovered at runtime (see below).
StructureMap IOC:
ObjectFactory.Initialize(
a =>;
{
a.Register(new SubpoenaKeysValidator());
a.For().Use(new MsSqlDatabase());
a.For().Use();
a.RegisterInterceptor(new LogInterceptor());
a.Scan(s=>;
{
s.TheCallingAssembly();
s.AssemblyContainingType();
s.WithDefaultConventions();
s.ConnectImplementationsToTypesClosing(typeof (AbstractValidator));
s.ConnectImplementationsToTypesClosing(typeof(IFormHandler));
s.ConnectImplementationsToTypesClosing(typeof(IQueryHandler));
}
);
});
ObjectFactory.AssertConfigurationIsValid();
This line: s.ConnectImplementationsToTypesClosing(typeof (AbstractValidator)); discovers all the implementations of AbstractValidator at runtime.
When all of this is in place all you have to do is define an AbstractValidator and at runtime it will Automagically be discovered and applied to it’s type.