Leverage EntLib Validation Application Block in MVC

UPDATE: This post is now out of date as of the CTP5 release of the MVC framework. For the lastest version, see this post here.

One of the areas of MVC that is still an open area in development is how best to handle UI validations.  There are numerous approaches currently being examined.

The Enterprise Library's VAB is great because it allows you to specify all your validation logic in your business layer where it belongs rather than in your UI.  Additionally, it comes with the PropertyProxyValidator to automatically display validation messages in the UI and avoid having to put hundreds of validators on a page.  However, the PropertyProxyValidator is a traditional ASP.NET validator control and does not work in the MVC framework.

The good news here is that duplicating the functionality of the PropertyProxyValidator in the MVC framework is not difficult.  Currently, the MVC framework comes with some built-in extension methods to enable the developer to easy create UI elements hanging off the HtmlHelper property.  I've written a couple of similar extension methods following the same pattern that essentially produce the same output as the ProperyProxyValidator.  This enables us to write UI code without cluttering our mark up with a ton of validator controls:

<%=Html.TextBox("FirstName", ViewData.Model.Contact.FirstName)%>
<%=Html.Validator("FirstName", ViewData.Model.ValidationResults)%>

Notice that a property called "ValidationResults" is being passed in to this Validator.  When the form is submitted, it goes to a controller action (let's say "Save" for this example):

public ActionResult Save(int id)
{
    Contact contact = new Contact();
    contact.ContactID = id;
    BindingHelperExtensions.UpdateFrom(contact, Request.Form);
 
    ValidationResults validationResults = Validation.Validate(contact);
    if (validationResults.IsValid)
    {
        contactManager.SaveContact(contact);
        return RedirectToAction(Views.Index);
    }
    else
    {
        ContactViewData contactViewData = new ContactViewData(contact);
        contactViewData.ValidationResults = validationResults;
        ViewData.Model = contactViewData;
        return View(Views.EditContact);
    }
} 

So if the contact object in this example is valid, we save it to the database and proceed as normal to the next redirect action.  However, if the contact is not valid, we redisplay the edit contact page again and this time we pass the ValidationResults along with the existing contact object in the Model to the View.  This Validator control will grab the appropriate validation messages for the specified control/property and display any validation messages for that specific control.  The following is the complete code for the Validator extension methods:

public static class ValidationControlExtensions
{
 
    public static string Validator(this HtmlHelper html, string controlToValidate, ValidationResults validationResults)
    {
        return Validator(html, controlToValidate, validationResults, new { style="color:Red;" });
    }
 
    public static string Validator(this HtmlHelper html, string controlToValidate, ValidationResults validationResults, object htmlAttributes)
    {
        return Validator(html, controlToValidate, controlToValidate, validationResults, htmlAttributes);
    }
 
    public static string Validator(this HtmlHelper html, string controlToValidate, string propertyName, ValidationResults validationResults, object htmlAttributes)
    {
        if (validationResults &#61;&#61; null)
        {
            return null;
        }

        var filteredResults = validationResults.Where(r => r.Key &#61;&#61; propertyName);
        if (filteredResults.Count() &#61;&#61; 0)
        {
            return null;
        }
 
        StringBuilder errorMessage = new StringBuilder();
        foreach (ValidationResult validationResult in filteredResults)
        {
            errorMessage.Append(validationResult.Message);
            errorMessage.Append("<br/>");
        }
 
        TagBuilder builder = new TagBuilder("span");
        IDictionary<string, object> attribueDictionary = new RouteValueDictionary(htmlAttributes);
        builder.Attributes = ToStringDictionary(attribueDictionary);
        builder.InnerHtml = errorMessage.ToString();
        return builder.ToString();
    }
 
    /// <summary>
    /// ToStringDictionary is Internal to System.Web.Mvc.HtmlHelper
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="dictionary"></param>
    /// <returns></returns>
    internal static Dictionary<string, string> ToStringDictionary<TKey, TValue>(IDictionary<TKey, TValue> dictionary)
    {
        if (dictionary == null)
        {
            return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        }
        return dictionary.ToDictionary<KeyValuePair<TKey, TValue>, string, string>(entry => Convert.ToString(entry.Key, CultureInfo.InvariantCulture), entry => Convert.ToString(entry.Value, CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase);
    }
}

One note here is that you can see I'm using a class called TagBuilder inside my method.  This is actually an internal class in the System.Web.Mvc assembly.  I just cracked open Reflector and did a complete copy/paste of that code into my own assembly so that I could leverage it.  But what would be really nice is if Microsoft would change the accessibility on that class to public to more easily enable scenarios like this when people want to build their own control extension methods.

Tweet Post Share Update Email RSS