Adding Unobtrusive Validation To MVCContrib Fluent Html

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!

11 Comments

  • Nice article, thanks for that.
    Would that work with MVC2, I mean can you method render HTML5 with MVC2.
    Thanks.

  • @George,
    >Nice article, thanks for that.

    >Would that work with MVC2, I mean can you method render HTML5 with MVC2.

    This is only really applicable for MVC 3 since the unobtrusive validation features were not available in MVC 2.

    That said, there is nothing specific that would stop you from writing something that generates the same data-val-* attributes in a similar manner using MVC 2.

  • The unobtrusive client validation dosen't work with jquery 1.6.1!

  • @m.w.,
    According to the jquery.validation homepage, they have tested successfully against 1.6.1 (http://bassistance.de/jquery-plugins/jquery-plugin-validation/), so I think it should work.

  • If you put this logic inside a custom behavior, how would you call the GetUnobtrusiveValidationAttributes method since there seems to be no way to access the current HtmlHelper instance that you're passing to it?

  • I debug this helper method to see where val-data attributes were coming from and they are not added at all. Does any one got this to work? It seems to get an already rendered input tag with validation and repeating out what already exits in the input tag. But if you just show the view for the first time how does it get the val attributes?
    @this.TextBox(x=>x.psajero[i].name).Class("css").IncludeUnobtrusiveValidationAttributes(Html)

  • Km3i0B Looking forward to reading more. Great article post.Really looking forward to read more. Awesome.

  • heel Lifts and heel Lifts are the same factor, also known as shoe inserts, it depends on where you live within the globe or who you're talking to, I think of heel Lifts as becoming height increase options and heel Lifts as leg length discrepancy options, each obviously being shoe inserts as they are inserted in to the shoe
    shoe lifts

  • t1XlMA I think this is a real great post.Thanks Again. Cool.

  • It makes a change to find decent content for once, I was getting tired of the retarded drivel I find on
    a daily basis, respect.

  • It is possible to speak infinitely on this question.


Comments have been disabled for this content.