Sep 23, 2014

jQuery Unobtrusive client side form validation for ASP.NET MVC



Here at VeriTech we are using a modified version of Microsoft's jQuery Unobtrusive Validation, a support library for jQuery and jQuery Validate Plugin which is shipped with ASP.NET MVC since it's third release. It enhances client side validation, by using unobtrusive data-* validation attributes instead of generated code, like in previous versions. Thus, it is easily expandable and doesn't pollute your code.

We also maintain the BForms open source framework, making it the perfect platform to implement and publish the improvements that we added to the way in which validation works. Here I am going to list some of those changes and also explain the reasons behind them.
Even if you use client side validation you must validate your data on the server as well, by checking the ModelState.IsValid property. Also you should call the ValidationMessageFor HTML helper responsible for displaying the validation error messages from your ModelState. Sadly, this implies that you are going to redraw the form, adding an unnecessary overhead. That's why we made some small improvements to the flow of validating a form. First of all, we are using a modified version of the showErrors method from the jQuery validation plugin. We added a second boolean parameter which denotes if the viewport should scroll to the first form error, improving the user experience.
Another thing that we added was an extension method for the ModelStateDictionary class, which gathers all the model errors in an easy to use way.
 public virtual BsJsonResult GetJsonErrorResult()
        {
            return new BsJsonResult(
                new Dictionary { { "Errors", this.ModelState.GetErrors() } },
                BsResponseStatus.ValidationError);
        }
By using all the methods above and an overload of jQuery's ajax method, which includes some new callback methods, including one for server validation errors, we managed to give the users a seamless experience, without needing to replace the whole form to render the errors.
$.bforms.ajax({
                url: ajaxUrl,
                data: submitData,                              
                validationError: function(response){
                    var validator = $('.validated-form').validate();
                    validator.showErrors(response.Errors, true);           
                }               
            });
On that note, one of the most asked questions that I heard on the subject of unobtrusive validation is how are you supposed to handle new HTML added to your page. I found that the best way is to remove the data values added to the form, named "validator" and "unobtrusiveValidation" and calling the jQuery.validator.unobtrusive.parse method on the new content.
var $form = $('.validated-form');

$form.removeData('validator');
$form.removeData('unobtrusiveValidation');

$.validator.unobtrusive.parse($form);
Another thing that you are probably going to need at some time is some custom validation rules. There are two steps that you must complete to be able to do that. First, you have to create a new validation attribute, which inherits the the ValidationAttribute class and the IClientValidatable interface. After that you should call the jQuery.validator.unobtrusive.adapters.add method, which has the following parameters:
  • adapterName : The name of the adapter to be added. This matches the name used in the data-val-nnnn HTML attribute (where nnnn is the adapter name)
  • params:  An array of parameter names (strings) that will be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and mmmm is the parameter name)
  • fn : The function to call, which adapts the values from the HTML attributes into jQuery Validate rules and/or messages.
Don't forget to parse your form again to be sure that the new validation rules are added.
As you can see, you can manually add the data-val-nnnnn HTML attributes to your form elements without using a validation attribute, but then you are not going to be able to use the server side validation methods.

This is the way in which you can implement a custom rule for mandatory checkboxes (for example a Terms and Conditions agreement) :
//server side code using validation attributes
public class BsMandatoryAttribute : ValidationAttribute, IClientValidatable
{
    /// 
    /// Returns true if the object is of type bool and it's set to true
    /// 
    public override bool IsValid(object value)
    {
        //handle only bool? and bool

        if (value.GetType() == typeof(Nullable))
        {
            return ((bool?)value).Value;
        }

        if (value.GetType() == typeof(bool))
        {
            return (bool)value;
        }

        throw new ArgumentException("The object must be of type bool or nullable bool", "value");
    }

    public IEnumerable GetClientValidationRules(ModelMetadata metadata,
        ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "mandatory"
        };
    }
}
}
//client side validation methods
jQuery.validator.addMethod('mandatory', function (value, elem) {
        var $elem = $(elem);
        if ($elem.prop('type') == 'checkbox') {
            if (!$elem.prop('checked')) {
                return false;
            }
        }
        return true;
    });

jQuery.validator.unobtrusive.adapters.addBool('mandatory');

Another improvement that we added to the validator is the way in which validation errors are displayed. By default a label is added after your input which contains the raw error message. We replaced the label with a span containing a Bootstrap Warning Glyphicon. We also used the Bootstrap Tooltip plugin, so that you can see the validation by hovering over the exclamation mark, as you can see below:

For a live demo of a form validated using our modified version of Microsoft’s Unobtrusive validation you can check the  BForms Login Demo Page

1 comment: