Integrating Bootstrap Error styling with MVC’s Unobtrusive Error Validation

A common component of many of the websites that I build are small administrative areas where clients are able to perform tasks such as downloading leads or scheduling appointments. Until recently, most of those websites were built to run on top of a content management framework--with the CMS providing most of the back-end interface that I deliver. I started using Twitter's Bootstrap framework to deliver a consisten look and feel with our ASP.NET MVC3 applications. Bootstrap provided me with a clean CSS grid system, clean controls, and a host of other components. The biggest disconnect between Bootstrap and MVC3 is the unobtrusive validation.

I built the example administration pages in my MongoDB membership provider project as shown here. It is a small, three controller area in the project that uses Bootstrap for all of its look and feel. I am using all of the standard Razor html controls for the form elements with the form styled according to the Bootstrap Base CSS.

Here is the CSHTML source for the username field:

        <div class="control-group">
            @Html.LabelFor(x => x.Username)
            <div class="controls">
                @Html.TextBoxFor(x => x.Username)
                @Html.ValidationMessageFor(x => x.Username)
            </div>
        </div>

And the corresponding HTML that has been generated:

        <div class="control-group">
            <label for="Username">Username</label>
            <div class="controls">
                <input data-val="true" data-val-required="Username is required" id="Username" name="Username" type="text" value="" />
                <span class="field-validation-valid" data-valmsg-for="Username" data-valmsg-replace="true"></span>
            </div>
        </div>

If I skip entering in a username when I submit the form, the unobtrusive validation will change the validation <span> class from field-validation-valid to field-validation-error. When using the provided CSS and HTML templating in a new MVC3 project, this will style the error message so that it is clearly marked. However, this is not the desired marking for Bootstrap. Bootstrap expects the error class name to be added to the <div class="control-group"> element. We can emulate this in the browser using Firebug, resulting in clear indication of the fields with errors. We add the error class to the control group div and the help-inline class to the validation span.

The first part of the solution is to add jQuery selections to our document ready routine to add the help-inline class to all validation spans:

    $('span.field-validation-valid, span.field-validation-error').each(function () {
        $(this).addClass('help-inline');
    });

The next piece requires some understanding of how the jquery.validate event model works. We actually do not need to worry about the unobtrusive portion of of the validation as it is only used to configure jquery.validate. When you first submit the form, control passes to jquery.validate functions submitHandler and invalidHandler. Once those are called, jquery.validate will still call any existing submit handlers for the form and prevent the form from being submitted. Inside the submit handler you can get the validation state of the form with the valid() function.

    $('form').submit(function () {
        if ($(this).valid()) {
            $(this).find('div.control-group').each(function () {
                if ($(this).find('span.field-validation-error').length == 0) {
                    $(this).removeClass('error');
                }                
            });
        }
        else {
            $(this).find('div.control-group').each(function () {
                if ($(this).find('span.field-validation-error').length > 0) {
                    $(this).addClass('error');
                }                
            });
        }
    });

Now we are adding and removing the necessary classnames so Bootstrap displays the error fields perfectly! That is until we encounter a server-side validation error. One more update and we'll be good to go

    $('form').each(function () {
        $(this).find('div.control-group').each(function () {
            if ($(this).find('span.field-validation-error').length > 0) {
                $(this).addClass('error');
            }
        });
    });

Have fun building with MVC3 and Bootstrap!