Adding Unobtrusive Validation To MVCContrib Fluent Html

Posted by srkirkland on ASP.net Weblogs See other posts from ASP.net Weblogs or by srkirkland
Published on Tue, 08 Mar 2011 18:05:10 GMT Indexed on 2011/03/09 0:11 UTC
Read the original article Hit count: 1058

ASP.NET MVC 3 includes a new unobtrusive validation strategy that utilizes HTML5 data-* attributes to decorate form elements.  Using a combination of jQuery validation and an unobtrusive validation adapter script that comes with MVC 3, those attributes are then turned into client side validation rules.

A Quick Introduction to Unobtrusive Validation

To quickly show how this works in practice, assume you have the following Order.cs class (think Northwind) [If you are familiar with unobtrusive validation in MVC 3 you can skip to the next section]:

public class Order : DomainObject
{
    [DataType(DataType.Date)]
    public virtual DateTime OrderDate { get; set; }
 
    [Required]
    [StringLength(12)]
    public virtual string ShipAddress { get; set; }
 
    [Required]
    public virtual Customer OrderedBy { get; set; }
}

Note the System.ComponentModel.DataAnnotations attributes, which provide the validation and metadata information used by ASP.NET MVC 3 to determine how to render out these properties.  Now let’s assume we have a form which can edit this Order class, specifically let’s look at the ShipAddress property:

@Html.LabelFor(x => x.Order.ShipAddress)
@Html.EditorFor(x => x.Order.ShipAddress)
@Html.ValidationMessageFor(x => x.Order.ShipAddress)

Now the Html.EditorFor() method is smart enough to look at the ShipAddress attributes and write out the necessary unobtrusive validation html attributes.  Note we could have used Html.TextBoxFor() or even Html.TextBox() and still retained the same results.

If we view source on the input box generated by the Html.EditorFor() call, we get the following:

<input type="text" value="Rua do Paço, 67" name="Order.ShipAddress" id="Order_ShipAddress" 
data-val-required="The ShipAddress field is required." data-val-length-max="12" 
data-val-length="The field ShipAddress must be a string with a maximum length of 12." 
data-val="true" class="text-box single-line input-validation-error">

As you can see, we have data-val-* attributes for both required and length, along with the proper error messages and additional data as necessary (in this case, we have the length-max=”12”).

And of course, if we try to submit the form with an invalid value, we get an error on the client:

image

Working with MvcContrib’s Fluent Html

The MvcContrib project offers a fluent interface for creating Html elements which I find very expressive and useful, especially when it comes to creating select lists.  Let’s look at a few quick examples:

@this.TextBox(x => x.FirstName).Class("required").Label("First Name:")
@this.MultiSelect(x => x.UserId).Options(ViewModel.Users)
@this.CheckBox("enabled").LabelAfter("Enabled").Title("Click to enable.").Styles(vertical_align => "middle")
 
@(this.Select("Order.OrderedBy").Options(Model.Customers, x => x.Id, x => x.CompanyName)
                    .Selected(Model.Order.OrderedBy != null ? Model.Order.OrderedBy.Id : "")
                    .FirstOption(null, "--Select A Company--")
                    .HideFirstOptionWhen(Model.Order.OrderedBy != null)
                    .Label("Ordered By:"))

These fluent html helpers create the normal html you would expect, and I think they make life a lot easier and more readable when dealing with complex markup or select list data models (look ma: no anonymous objects for creating class names!).

Of course, the problem we have now is that MvcContrib’s fluent html helpers don’t know about ASP.NET MVC 3’s unobtrusive validation attributes and thus don’t take part in client validation on your page.  This is not ideal, so I wrote a quick helper method to extend fluent html with the knowledge of what unobtrusive validation attributes to include when they are rendered.

Extending MvcContrib’s Fluent Html

Before posting the code, there are just a few things you need to know.  The first is that all Fluent Html elements implement the IElement interface (MvcContrib.FluentHtml.Elements.IElement), and the second is that the base System.Web.Mvc.HtmlHelper has been extended with a method called GetUnobtrusiveValidationAttributes which we can use to determine the necessary attributes to include.  With this knowledge we can make quick work of extending fluent html:

public static class FluentHtmlExtensions
{
    public static T IncludeUnobtrusiveValidationAttributes<T>(this T element, HtmlHelper htmlHelper) 
        where T : MvcContrib.FluentHtml.Elements.IElement
    {
        IDictionary<string, object> validationAttributes = htmlHelper
            .GetUnobtrusiveValidationAttributes(element.GetAttr("name"));
 
        foreach (var validationAttribute in validationAttributes)
        {
            element.SetAttr(validationAttribute.Key, validationAttribute.Value);
        }
 
        return element;
    }
}

The code is pretty straight forward – basically we use a passed HtmlHelper to get a list of validation attributes for the current element and then add each of the returned attributes to the element to be rendered.

The Extension In Action

Now let’s get back to the earlier ShipAddress example and see what we’ve accomplished.  First we will use a fluent html helper to render out the ship address text input (this is the ‘before’ case):

@this.TextBox("Order.ShipAddress").Label("Ship Address:").Class("class-name")

And the resulting HTML:

<label id="Order_ShipAddress_Label" for="Order_ShipAddress">Ship Address:</label>
<input type="text" value="Rua do Paço, 67" name="Order.ShipAddress"
 id="Order_ShipAddress" class="class-name">

Now let’s do the same thing except here we’ll use the newly written extension method:

@this.TextBox("Order.ShipAddress").Label("Ship Address:")
.Class("class-name").IncludeUnobtrusiveValidationAttributes(Html)

And the resulting HTML:

<label id="Order_ShipAddress_Label" for="Order_ShipAddress">Ship Address:</label>
<input type="text" value="Rua do Paço, 67" name="Order.ShipAddress"
 id="Order_ShipAddress" data-val-required="The ShipAddress field is required."
 data-val-length-max="12"
 data-val-length="The field ShipAddress must be a string with a maximum length of 12."
 data-val="true" class="class-name">

Excellent!  Now we can continue to use unobtrusive validation and have the flexibility to use ASP.NET MVC’s Html helpers or MvcContrib’s fluent html helpers interchangeably, and every element will participate in client side validation.

image

Wrap Up

Overall I’m happy with this solution, although in the best case scenario MvcContrib would know about unobtrusive validation attributes and include them automatically (of course if it is enabled in the web.config file).  I know that MvcContrib allows you to author global behaviors, but that requires changing the base class of your views, which I am not willing to do.

Enjoy!

© ASP.net Weblogs or respective owner

Related posts about code

Related posts about ASP.NET