Enterprise Library Validation Application Block with MVC Binders

A while back, I blogged about using the Enterprise Library Validation Application Block (VAB) with ASP.NET MVC. As MVC has matured as a framework, this scenario has becoming simpler.  In early releases of MVC, I implemented the execution of the VAB validation in the controller methods.  However, I now prefer to put that logic in the binders themselves.  In earlier versions of the framework, the model binders that came out of the box dealt well with simple objects but if you had more complex View Models (as described in this post) then you had to roll your own binder.  With the latest releases of MVC, the DefaultModelBinder that comes OOTB with MVC is now quite robust and is even capable of dealing with those more complex binding scenarios.  Hence, my preferred method for performing the valiation is best described in this post by David Hayden here.

However, the one issue with that method is that, although the binder deals well with the complex object, you'll still run into the same issue with the VAB when it comes to the Key property of the validation messages.  That is, the key is the name of the business object property itself which does not always match the property of the view model.  For example, let's revisit the example from my previous post and update it to use this new method.  We have our Model passed to our view defined as:

public class ContactViewData
{
    public Contact Contact { get; set; }
    public IEnumerable<State> StateList { get; set; }
}

We bind to our textbox like this:

<%=Html.TextBox("Contact.FirstName")%>

Or, if you prefer the MVC Futures approach, like this:

<%=Html.TextBoxFor(m => m.Contact.FirstName) %>

So our mismatch is that when the FirstName is invalid, the key for the validation result will be "FirstName" but we were binding to "Contact.FirstName".  You have two basic options to tackle this type of situation:

Option 1 – Prepend appropriate prefix with your binder

This is basically a re-write of my original method by utilizing a custom model binder that derives from the DefaultModelBinder.  This is also a hybrid of Hayden's approach:

public class ContactModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var validator = ValidationFactory.CreateValidator(bindingContext.ModelType);
        var validationResults = validator.Validate(bindingContext.Model);
 
        foreach (var result in validationResults)
        {
            string mvcKey = GetMvcKey(result);
            bindingContext.ModelState.AddModelError(mvcKey, result.Message);
        }
    }
 
    static Dictionary<Type, string> propertyPrefixMap = new Dictionary<Type, string>()
    {
        { typeof(Contact), "Contact" },
        { typeof(Address), "Contact.Address" }
    };
 
    /// <summary>
    /// Converts an Enterprise Library ValidationResult into the correct "key" to be used by MVC Views.
    /// </summary>
    /// <param name="validationResult"></param>
    /// <returns></returns>
    static string GetMvcKey(ValidationResult validationResult)
    {
        string result;
        propertyPrefixMap.TryGetValue(validationResult.Target.GetType(), out result);
 
        if (string.IsNullOrEmpty(result))
        {
            return validationResult.Key;
        }
        else
        {
            return result + "." + validationResult.Key;
        }
    }
}

This upside is that you enable you model and validations to match precisely.  The downside is that this model binder does not have a lot of re-use if you're not using this model in multiple views.

Option 2 – Set your Html.ValidationMessage() differently

In this typical scenario, you'd set up your view like this:

<%=Html.TextBox("Contact.FirstName")%>
<%=Html.ValidationMessage("Contact.FirstName") %>

Of course, this leads to the issue described above. As an alternative, you could use different name parameters like this:

<%=Html.TextBox("Contact.FirstName")%>
<%=Html.ValidationMessage("FirstName") %>

The upside is that you could leverage a more re-usable binder as described in Hayden's post.  The downside is that it seems a little counter-intuitive to be using different parameters on the TextBox() and ValiationMessage() extension methods to represent the same business object property.  Perhaps this mis-match "feels" a little better with the MvcFutures syntax:

<%=Html.TextBoxFor(m => m.Contact.FirstName) %>
<%=Html.ValidationMessage("FirstName") %>

Whichever way you end up choosing, you certainly have a couple of decent options.  The fact that MVC was designed in such a flexible way to be able to give you these options in the first place speaks volumes.