Tuesday, June 16, 2015

Unobtrusive Client Validation in ASP.NET MVC 3 & Overriding Unobtrusive Client Side Validation Settings in ASP.NET MVC 3



Introduction

In ASP.NET MVC 2, we shipped both client- and server-side validation support. The client-side validation that we included in MVC 2 was a custom validation system written against ASP.NET Ajax. We also included an experimental version written against jQuery in the MVC Futures project.
In ASP.NET MVC 3 Beta, we’ve updated the runtime to enable a feature we’re calling “Unobtrusive Client Validation”. We have also created a consumer for these unobtrusive client validation attributes that uses jQuery and jQuery Validate to perform the validation on our behalf.

Disclaimer

This blog post talks about ASP.NET MVC 3 Beta, which is a pre-release version. Specific technical details may change before the final release of MVC 3. This release is designed to elicit feedback on features with enough time to make meaningful changes before MVC 3 ships, so please comment on this blog post or contact me if you have comments.

Rendering: Traditional

The client-side validation system in MVC 2 was decoupled from the server-side validation system through the use of JSON. With a model like this:
?
1
2
3
4
5
6
7
8
9
10
public class ValidationModel {
    [Required]
    public string FirstName { get; set; }
 
    [Required, StringLength(60)]
    public string LastName { get; set; }
 
    [Range(1, 130)]
    public int Age { get; set; }
}
With client-side validation enabled, you get the following markup: (some unimportant HTML removed)
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<label for="FirstName">FirstName</label>
<input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="" />
<span class="field-validation-valid" id="FirstName_validationMessage"></span>
 
<label for="LastName">LastName</label>
<input class="text-box single-line" id="LastName" name="LastName" type="text" value="" />
<span class="field-validation-valid" id="LastName_validationMessage"></span>
 
<label for="Age">Age</label>
<input class="text-box single-line" id="Age" name="Age" type="text" value="" />
<span class="field-validation-valid" id="Age_validationMessage"></span>
 
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"FieldName":"FirstName","ReplaceValidationMessageContents":true,"ValidationMessageId":"FirstName_validationMessage","ValidationRules":[{"ErrorMessage":"The FirstName field is required.","ValidationParameters":{},"ValidationType":"required"}]},{"FieldName":"LastName","ReplaceValidationMessageContents":true,"ValidationMessageId":"LastName_validationMessage","ValidationRules":[{"ErrorMessage":"The LastName field is required.","ValidationParameters":{},"ValidationType":"required"},{"ErrorMessage":"The field LastName must be a string with a maximum length of 60.","ValidationParameters":{"max":60},"ValidationType":"length"}]},{"FieldName":"Age","ReplaceValidationMessageContents":true,"ValidationMessageId":"Age_validationMessage","ValidationRules":[{"ErrorMessage":"The field Age must be between 1 and 130.","ValidationParameters":{"min":1,"max":130},"ValidationType":"range"},{"ErrorMessage":"The Age field is required.","ValidationParameters":{},"ValidationType":"required"},{"ErrorMessage":"The field Age must be a number.","ValidationParameters":{},"ValidationType":"number"}]}],"FormId":"form0","ReplaceValidationSummary":true,"ValidationSummaryId":"validationSummary"});
//]]>
</script>
With unobtrusive JavaScript turned off, you will get this behavior, which is the same as MVC 2. (Make sure you scroll all the way to the right, to see the extent of the JSON.)

Rendering: Unobtrusive

When unobtrusive Ajax mode is enabled in MVC, the HTML that we generate looks significantly different:
?
1
2
3
4
5
6
7
8
9
10
11
<label for="FirstName">FirstName</label>
<input class="text-box single-line" data-val="true" data-val-required="The FirstName field is required." id="FirstName" name="FirstName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
 
<label for="LastName">LastName</label>
<input class="text-box single-line" data-val="true" data-val-length="The field LastName must be a string with a maximum length of 60." data-val-length-max="60" data-val-required="The LastName field is required." id="LastName" name="LastName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="LastName" data-valmsg-replace="true"></span>
 
<label for="Age">Age</label>
<input class="text-box single-line" data-val="true" data-val-number="The field Age must be a number." data-val-range="The field Age must be between 1 and 130." data-val-range-max="130" data-val-range-min="1" data-val-required="The Age field is required." id="Age" name="Age" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Age" data-valmsg-replace="true"></span>
(Again, make sure you scroll all the way to the right, to see the extent of the HTML attributes.)
The biggest change is obviously that we don’t emit the JSON blob any more. Instead, we’ve replaced the JSON with HTML 5-compatible attributes which describe the validators to be attached to the input fields. We’ve also attached some HTML attributes to the validation message spans so that they can be related to the input field they’re attached to. Since the HTML attributes are all directly attached to the HTML elements they affect, we were also able to get rid of several cases of auto-generated IDs that were no longer necessary.

Enabling Unobtrusive JavaScript

In MVC 3, we have a single flag to turn on unobtrusive JavaScript mode, which enables both unobtrusive Ajax and unobtrusive client validation. Unobtrusive JavaScript mode is turned off by default for backward compatibility with projects upgraded from MVC 1.0 and MVC 2. However, we have turned it on in the MVC 3 project template, so new projects will begin using the unobtrusive JavaScript support by default. Additionally, you will need to enable client side validation (which remains off by default).
To turn unobtrusive JavaScript mode on/off and enable/disable client validation by default for the entire application, you can use Web.config:
?
1
2
3
4
5
6
<configuration>
    <appSettings>
        <add key="ClientValidationEnabled" value="true"/>
        <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    </appSettings>
</configuration>
You can also turn them on or off with code:
?
1
2
HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
Using code to turn these features on or off actually behaves contextually. If those lines of code are present in your Global.asax file, then it turns unobtrusive JavaScript and client validation on or off for the whole application. If they appear within your controller or view, on the other hand, it will turn the features on or off for the current action only.
In addition to setting the flag, you will also need to include three script files: jQuery (~/Scripts/jquery-1.4.1.js), jQuery Validate (~/Scripts/jquery.validate.js) and the MVC plugin for unobtrusive client validation with jQuery Validate (~/Scripts/jquery.validate.unobtrusive.js).
An interesting note: since there is no actual JavaScript being emitted when you use unobtrusive client validation, if you forget to include the scripts, you won’t see any errors when loading the page; the form values will simply not validate on the client side.

How Attributes Are Generated

When an input field has any client side validation rule attached to it, it will receive the data-val="true" attribute to trigger unobtrusive client validation.
Important note: jQuery Validate requires your input elements to be inside of a <form> element in order to be validated. In addition, MVC 3 requires that you have called Html.BeginForm() to render this form, so that it can find its book-keeping object to help render the HTML attributes. Starting with MVC 4, we've eliminated the need for Html.BeginForm(), but the requirement for the HTML <form> element is still there.
For each client validation rule, an attribute is added with data-val-rulename="message". If the validators wants to the use the default client-side validation message, you can leave the attribute value as an empty string. Then, for each parameter in the client validation rule, an attribute is added with data-val-rulename-paramname="paramvalue".
For each Html.ValidationMessage call, the generated <span> will have data-valmsg-for="inputname" and data-valmsg-replace="true/false" attached to it.
If you call Html.ValidationSummary, the generated <div> will have data-valmsg-summary="true" applied to it.

Bridging HTML and jQuery Validate: Adapters

Writing a client-side validator involves two steps: writing the validator for jQuery Validate, and writing the adapter which takes the parameter values from the HTML attributes and turns it into jQuery Validate metadata. The former topic is not in the scope of this blog post (since it’s really not MVC specific).
There is an adapter collection available at jQuery.validator.unobtrusive.adapters. Hanging off the adapter collection is the adapter registration method (add) and three helpers that can be used to register very common types of adapters (addBool, addSingleVal, and addMinMax).

Boolean validators

The most common form of validator in jQuery Validate is a boolean validator; that is, the only data the validator needs to know is whether it’s on or not. Examples of boolean validators in jQuery Validate include “creditcard”, “date”, “digits”, “email”, “number”, “required”, and “url”.
To automatically create an adapter for a boolean validator, you can call the following helper method:
?
1
jQuery.validator.unobtrusive.adapters.addBool(adapterName, [ruleName]);
adapterName is the name of the adapter, and matches the name of the rule in the HTML element.
ruleName is the name of the jQuery Validate rule, and is optional. If it's not provided, then the adapterName is used.

Single value validators

A single value validator in jQuery Validate is one whose rule receives just a single value. An example of a single rule validators in jQuery Validate is "accept".
To automatically create an adapter for a single value validator, you can call the following helper method:
?
1
jQuery.validator.unobtrusive.adapters.addSingleVal(adapterName, attribute, [ruleName]);
adapterName is the name of the adapter, and matches the name of the rule in the HTML element.
attribute is the postfix name of the HTML attribute that contains the value for the validator (for example, if the HTML attribute is data-val-myvalidator-myvalue, then the value for attribute would be "myvalue").
ruleName is the name of the jQuery Validate rule, and is optional. If it's not provided, then the adapterName is used.

Min/max validators

A min/max validator is actually a set of validators that represent the possibility of validating for a minimum value, a maximum value, or both. The rule you use depends on whether you have one or the other values (or both). Examples of min/max validators in jQuery Validate include "min"/"max"/"range" and "minlength"/"maxlength"/"rangelength".
To automatically create an adapter for a min/max validator, you can call the following helper method:
?
1
2
jQuery.validator.unobtrusive.adapters.addMinMax(adapterName, minRuleName, maxRuleName,
                                      minMaxRuleName, [minAttribute, [maxAttribute]]);
adapterName is the name of the adapter, and matches the name of the rule in the HTML element.
minRuleName is the name of jQuery Validate rule to be used when only a minimum value is provided.
maxRuleName is the name of jQuery Validate rule to be used when only a maximum value is provided.
minMaxRuleName is the name of jQuery Validate rule to be used when both a minimum value and a maximum value are provided.
minAttribute is the HTML attribute name for the minimum value, and is optional. If it is not provided, it is assumed to be "min".
maxAttribute is the HTMl attribute name for the maximum value, and is optional. If it is not provided, it is assumed to be "max".

Custom adapters for unusual validators

If the validator you're writing doesn't match one of the patterns above, or you need to do some interesting processing on the HTML values before sending along the information to jQuery Validate, then you'll have to write your own adapter method.
To add a custom adapter for a validator, you can call the following method:
?
1
jQuery.validator.unobtrusive.adapters.add(adapterName, [params], fn);
adapterName is the name of the adapter, and matches the name of the rule in the HTML element.
params is an array of parameter names that you're expecting in the HTML attributes, and is optional. If it is not provided, then it is presumed that the validator has no parameters.
fn is a function which is called to adapt the HTML attribute values into jQuery Validate rules and messages. The function will receive a single parameter which is an options object with the following values in it:
element
The HTML element that the validator is attached to
form
The HTML form element
message
The message string extract from the HTML attribute
params
The array of name/value pairs of the parameters extracted from the HTML attributes
rules
The jQuery rules array for this HTML element. The adapter is expected to add item(s) to this rules array for the specific jQuery Validate validators that it wants to attach. The name is the name of the jQuery Validate rule, and the value is the parameter values for the jQuery Validate rule.
messages
The jQuery messages array for this HTML element. The adapter is expected to add item(s) to this messages array for the specific jQuery Validate validators that it wants to attach, if it wants a custom error message for this rule. The name is the name of the jQuery Validate rule, and the value is the custom message to be displayed when the rule is violated.

Parsing New HTML For Validation

The unobtrusive client validation script automatically parses the initial set of HTML for validation rules when the page has finished loading. If your page dynamically adds new HTML content (perhaps throught Ajax or through client-side application code), you may wish to parse that new HTML for client validation on the new HTML elements.
To parse new HTML, you can call the jQuery.validator.unobtrusive.parse() method, passing it a selector for the HTML that you would like to be parsed. You can also call the jQuery.validator.unobtrusive.parseElement() function to parse a single HTML element.
  Introduction:
          By default, client side validation in ASP.NET MVC 3 leverages unobtrusive javascript and famous jQuery validation plugin. The jQuery validation plugin makes client side validation very straightforward. With this plugin, you have a lot of options to customize the client side validation. But unfortunately, ASP.NET MVC 3 internally initialize the jQuery validation plugin and does not provide you an option to customize the validation settings(options). In this article, I will show you how to customize(override) the jQuery validation settings(options).
    Description:
          Let's say you are creating an ASP.NET MVC 3 application with unobtrusive client side validation. In this application, you need to override the default validation settings(options). Now, let's say you need to only validate the form fields when the form is submitted. You can make this possible by adding this script at the bottom of your page,
        $(function() {
            var settngs = $.data($('form')[0], 'validator').settings;
            settngs.onkeyup = false;
            settngs.onfocusout = false;
        });
          In the above code, I am first getting validator settings object. Then, I am setting the onkeyup and onfocusout properties of validator settings object to false. This will simply disable the validation on blur and key up. Now, let's say you need to disable the validation on input fields with class ignore. (Note, you can also disable it by directly adding the cancel class to your submit button) You can make this possible by adding this script at the bottom of your page, 
         
        $(function() {
            var settngs = $.data($('form')[0], 'validator').settings;
            settngs.ignore = ".ignore";
        });
          In the above code, I am just setting the ignore property of validator settings object to .ignore. This will tell the jQuery validation plugin to ignore all input elements with class ignore during validation. Now, If you need to do something whenever an input element is marked as valid or invalid.  You can make this possible by adding this script at the bottom of your page, 

        $(function() {
            var settngs = $.data($('form')[0], 'validator').settings;
            var oldErrorFunction = settngs.errorPlacement;
            var oldSucessFunction = settngs.success;
            settngs.errorPlacement = function (error, inputElement) {
                //Do something here
                oldErrorFunction(error, inputElement);
            }
            settngs.success = function (error) {
                //Do something here
                oldSucessFunction(error);
            }
        });
          In the above code, I am first getting the callback functions for success and errorPlacement events. Then, I am registering new callback functions for success and errorPlacement events. Inside these new callback functions, I am invoking the previously registered callback function. Similarly, you can customize(override) the different options of jQuery validation plugin. A complete list of jQuery validation options can be found here.   

    Summary:

          The jQuery validation plugin is the most popular framework for client side validation. By default, ASP.NEt MVC 3 uses this plugin to perform client side validation. This plugin allows you to customize(override) various validation settings(options).  In this article, I showed you how you can customize(override) various validation settings(options) using ASP.NET MVC 3. Hopefully you will enjoy this article too.

No comments:

Post a Comment