Jonas Stawski

Everything .NET and More

MVC Unobtrusive Validation with Twitter Bootstrap

ASP.NET MVC has great built-in support for client and server side validation. On the client side it uses jQuery Validation plugin with a custom client side library which parses the form’s elements and automatically creates validation rules from their attributes. Here’s a great tutorial that goes into details on Unobtrusive Validation with ASP.NET MVC and Razor.

The validation works great with the built in templates used by Visual Studio, but as soon as you want to change the UI things start to get messy. More specifically, if you are using Twitter Bootstrap and want to follow their Validation States guidelines then you need a custom solution that integrates with the current unobtrusive validation. I have found that the current documentation for both the jQuery Validation and the Unobstrusive Validation are quite lacking, so I embraced in a source code reading crusade to try to understand it in more details.

Let’s start with a simple form using Bootstrap. The Model is as follows:

   1: public class AccountModel
   2: {
   3:     [Required]
   4:     [MaxLength(10)]
   5:     [Display(Name = "First Name")]
   6:     public string FirstName { get; set; }
   8:     [Required]
   9:     [MaxLength(10)]
  10:     [Display(Name = "Last Name")]
  11:     public string LastName { get; set; }
  13:     [Required]
  14:     [StringLength(10, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
  15:     [DataType(DataType.Password)]
  16:     [Display(Name = "Password")]
  17:     public string Password { get; set; }
  19:     [DataType(DataType.Password)]
  20:     [Display(Name = "Confirm password")]
  21:     [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
  22:     public string ConfirmPassword { get; set; }
  23: }

The HTML View:

   1: @using (Html.BeginForm("Index", "Home", FormMethod.Post))
   2: {
   3:     <div class="form-group">
   4:         @Html.LabelFor(model => model.FirstName, new { @class = "control-label" })
   5:         @Html.TextBoxFor(model => model.FirstName, new { @class="form-control" })
   6:         @Html.ValidationMessageFor(model => model.FirstName)
   7:     </div>
   8:     <div class="form-group">
   9:         @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
  10:         @Html.TextBoxFor(model => model.LastName, new { @class="form-control" })
  11:         @Html.ValidationMessageFor(model => model.LastName)
  12:     </div>
  13:     <div class="form-group">
  14:         @Html.LabelFor(model => model.Password, new { @class = "control-label" })
  15:         @Html.PasswordFor(model => model.Password, new { @class="form-control" })
  16:         @Html.ValidationMessageFor(model => model.Password)
  17:     </div>
  18:     <div class="form-group">
  19:         @Html.LabelFor(model => model.ConfirmPassword, new { @class = "control-label" })
  20:         @Html.PasswordFor(model => model.ConfirmPassword, new { @class="form-control" })
  21:         @Html.ValidationMessageFor(model => model.ConfirmPassword)
  22:     </div>
  24:     <button type="submit" class="btn btn-default">Register</button>
  25: }
  27: @section Scripts {
  28:     @Scripts.Render("~/bundles/jqueryval")
  29: }

Figure 1Running this and clicking Register results in the following styleless page. Notice on Figure 1, the label, textbox, and message are all black. If we view source (Figure 2) then we notice that the input element was added the class input-validation-error and the error message span was added the field-validation-error class. At this point we have two options: we either create those CSS classes or simply follow the Bootstrap guidelines, which will give us a better user experience. The first option is the easiest one and we don’t need a tutorial for that, but the second one is a little more complex and, in my opinion, the right approach. To do so, we need to accomplish changing the classes of the span error messages to help-block, add the class has-error to the parent div when it is invalid, and remove the has-error class when it becomes valid. Figure 3 shows what the end result of the HTML should be. In order to accomplish all these tasks, we need to get ahold of the original validate object and change the settings by modifying the errorClass and the errorPlacement and success events (functions).

Figure 1

Figure 2
Figure 2

Figure 3
Figure 3

To change the error messages class to help-block we use jQuery and add the class for all the elements found by the selector:

   1: $('span.field-validation-valid, span.field-validation-error').each(function () {
   2:   $(this).addClass('help-block');
   3: });

Then we change the errorClass as follows:

   1: var $form = $('form');
   2: var $validate = $form.validate();
   3: var errorClass = "has-error";
   4: $validate.settings.errorClass = errorClass;

Then we modify the errorPlacement and success methods. We need to keep in mind that the jquery.validate.unobtrusive.js is also using these methods to do their magic, so if we override them then we will breaking existing functionality. Since there is no method overriding in Javascript, we simply get ahold of the original function and call it within our new function.

   1: var previousEPMethod = $validate.settings.errorPlacement;
   2: $validate.settings.errorPlacement = $.proxy(function (error, inputElement) {
   3:     if (previousEPMethod) {
   4:         previousEPMethod(error, inputElement);
   5:     }
   6:     inputElement.parent().addClass(errorClass);
   7: }, $form[0]);
   9: var previousSuccessMethod = $validate.settings.success;
  10: $validate.settings.success = $.proxy(function (error) {
  11:     //we first need to remove the class, cause the unobtrusive success method removes the node altogether
  12:     error.parent().parent().removeClass(errorClass);
  13:     if (previousSuccessMethod) {
  14:         previousSuccessMethod(error);
  15:     }
  16: });

The entire code is below. You can create it’s own javascript file and include it within your layout file as to use it for all your forms.

   1: $(function () {
   2:     $('span.field-validation-valid, span.field-validation-error').each(function () {
   3:         $(this).addClass('help-block');
   4:     });
   6:     var $form = $('form');
   7:     var $validate = $form.validate();
   8:     var errorClass = "has-error";
   9:     $validate.settings.errorClass = errorClass;
  10:     var previousEPMethod = $validate.settings.errorPlacement;
  11:     $validate.settings.errorPlacement = $.proxy(function (error, inputElement) {
  12:         if (previousEPMethod) {
  13:             previousEPMethod(error, inputElement);
  14:         }
  15:         inputElement.parent().addClass(errorClass);
  16:     }, $form[0]);
  18:     var previousSuccessMethod = $validate.settings.success;
  19:     $validate.settings.success = $.proxy(function (error) {
  20:         //we first need to remove the class, cause the unobtrusive success method removes the node altogether
  21:         error.parent().parent().removeClass(errorClass);
  22:         if (previousSuccessMethod) {
  23:             previousSuccessMethod(error);
  24:         }
  25:     });
  26: });

Keep in mind this code only works as expected when there is one form on the page but the end result is a nicely styled and explicit page that will catch the user’s attention:


Happy programming!

Comments (4) -

Wendel Fabian Chinsam
Wendel Fabian Chinsam

Hi Jonas.

Firstly thank you for the amazing article has helped my quite a lot.  Have you however found a solution to making the validation work with more than form on the page or have you found an alternate solution to this?  Any response would be greatly appreciated.


Howard Cheng
Howard Cheng

Some changes I had to make to this script:

1. Line 15: inputElement -> $(inputElement).

2. Line 19: add inputElement to the delegate parameters: $.proxy(function (error, inputElement)

3. Line 21: error.parent() -> $(inputElement)

Also, Bootstrap provides a "has-success" class, so you can add it in line 21 and remove it in line 15 if you want.


Make Money Online
United States Make Money Online

good content, good article. i wish there are more like this one! thanks


Oleg Deribas
Oleg Deribas

It looks like this doesn't work with bootstrap checkboxes and radios as they are wrapped in additional div.


Add comment