Client Validation

Client validation for data annotation attributes is on by default with the MVC framework. As an example, look at the Title and Price properties of the Album class:

[Required(ErrorMessage = "An Album Title is required")]
[StringLength(160)]
public string   Title      { get; set; }

[Required(ErrorMessage = "Price is required")]
[Range(0.01, 100.00,
    ErrorMessage = "Price must be between 0.01 and 100.00")]
public decimal Price       { get; set; }

The data annotations make these properties required, and also put in some restrictions on the length and the range of the values the properties hold. The model binder in ASP.NET MVC performs server-side validation against these properties when it sets their values. These built-in attributes also trigger client-side validation. Client-side validation relies on the jQuery validation plugin.

jQuery Validation

As mentioned earlier, the jQuery validation plugin (jquery.validate) exists in the Scripts folder of a new MVC 3 application by default. If you want client-side validation, you'll need to have a couple script tags in place. If you look in the Edit or Create views in the StoreManager folder, you'll find the following lines inside:

<script src="@Url.Content("∼/Scripts/jquery.validate.min.js")" 
    type="text/javascript"></script>
<script src="@Url.Content("∼/Scripts/jquery.validate.unobtrusive.min.js")"
    type="text/javascript"></script>

AJAX Settings in web.config

By default, unobtrusive JavaScript and client-side validation are enabled in an ASP.NET MVC application. However, you can change the behavior through web.config settings. If you open the root-level web.config file in a new application, you'll see the following appSettings configuration section:

  <appSettings>
    <add key="ClientValidationEnabled" value="true"/> 
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/> 
  </appSettings>

If you want to turn off either feature throughout the application, you can change either setting to false. In addition, you can also control these settings on a view-by-view basis. The HTML helpers EnableClientValidation and EnableUnobtrusiveJavascript override the configuration settings inside a specific view.

The primary reason to disable either feature is to maintain backward compatibility with existing custom scripts that rely on the Microsoft AJAX library instead of jQuery.

The first script tag loads the minified jQuery validation plugin. jQuery validation implements all the logic needed to hook into events (like submit and focus events) and execute client-side validation rules. The plugin provides a rich set of default validation rules.

The second script tag includes Microsoft's unobtrusive adapter for jQuery validation. The code inside this script is responsible for taking the client-side metadata the MVC framework emits, and adapting (transforming) the metadata into data jQuery validation will understand (so it can do all the hard work). Where does the metadata come from? First, remember how you built an edit view for an album? You used EditorForModel inside your views, which uses the Album editor template in the Shared folder. The template has the following code:

<p>
    @Html.LabelFor(model => model.Title)
    @Html.TextBoxFor(model => model.Title)
    @Html.ValidationMessageFor(model => model.Title)
</p>
<p>
    @Html.LabelFor(model => model.Price)
    @Html.TextBoxFor(model => model.Price)
    @Html.ValidationMessageFor(model => model.Price)
</p>

The TextBoxFor helper is the key. The helper builds out inputs for a model based on metadata. When TextBoxFor sees validation metadata, such as the Required and StringLength annotations on Price and Title, it can emit the metadata into the rendered HTML. The following markup is the editor for the Title property:

<input 
   data-val="true" 
   data-val-length="The field Title must be a string with a maximum length of 160."
   data-val-length-max="160" data-val-required="An Album Title is required" 
   id="Title" name="Title" type="text" value="Greatest Hits" />

Once again, you see data dash attributes. It's the responsibility of the jquery.validate.unobtrusive script to find elements with this metadata (starting with data-val=“true”) and to interface with the jQuery validation plugin to enforce the validation rules expressed inside the metadata. jQuery validation can run rules on every keypress and focus event, giving a user instant feedback on erroneous values. The validation plugin also blocks form submission when errors are present, meaning you don't need to process a request doomed to fail on the server.

To understand how the process works in more detail, it's useful to look at a custom client validation scenario, shown in the next section.

Custom Validation

In Chapter 6 you wrote a MaxWordsAttribute validation attribute to validate the number of words in a string. The implementation looked like the following:

public class MaxWordsAttribute : ValidationAttribute        
{
    public MaxWordsAttribute(int maxWords)
        :base("Too many words in {0}")
    {
        MaxWords = maxWords;           
    }

    public int MaxWords { get; set; }

    protected override ValidationResult IsValid(
        object value, 
        ValidationContext validationContext)
    {
        if (value != null)
        {
            var wordCount = value.ToString().Split(’ ‘).Length;
            if (wordCount > MaxWords)
            {
                return new ValidationResult(
                    FormatErrorMessage(validationContext.DisplayName)
                );
            }
        }
        return ValidationResult.Success;
    }        
}

You can use the attribute as the following code demonstrates, but the attribute provides only server-side validation support:

[Required(ErrorMessage = "An Album Title is required")]
[StringLength(160)]
[MaxWords(10)]
public string   Title      { get; set; }

To support client-side validation, you need your attribute to implement an interface discussed in the next section.

IClientValidatable

The IClientValidatable interface defines a single method: GetClientValidationRules. When the MVC framework finds a validation object with this interface present, it invokes GetClientValidationRules to retrieve—you guessed it—a sequence of ModelClientValidationRule objects. These objects carry the metadata, or the rules, the framework sends to the client.

You can implement the interface for the custom validator with the following code:

public class MaxWordsAttribute : ValidationAttribute, 
                                 IClientValidatable
{
    ...

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
             ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
        rule.ValidationParameters.Add("wordcount", WordCount);
        rule.ValidationType = "maxwords";
        yield return rule;
    }
}

If you think about the scenario, there are a few pieces of information you'd need on the client to run the validation:

  • What error message to display if the validation fails
  • How many words are allowed
  • An identifier for a piece of JavaScript code that can count the words

This information is exactly what the code is putting into the rule that is returned. Notice you can return multiple rules if you need to trigger multiple types of validation on the client.

The code puts the error message into the rule's ErrorMessage property. Doing so allows the server-side error message to exactly match the client-side error message. The ValidationParameters collection is a place to hold parameters you need on the client, like the maximum number of words allowed. You can put additional parameters into the collection if you need them, but note the names are significant and have to match names you see in client script. Finally, the ValidationType property identifies a piece of JavaScript code you need on the client.

The MVC framework takes the rules given back from the GetClientValidationRules method and serializes the information into data dash attributes on the client:

<input 
  data-val="true" 
  data-val-length="The field Title must be a string with a maximum length of 160." 
  data-val-length-max="160" 
  data-val-maxwords="Too many words in Title"
  data-val-maxwords-wordcount="10"
  data-val-required="An Album Title is required" id="Title" name="Title" 
  type="text" value="For Those About To Rock We Salute You" />

Notice how maxwords appears in the attribute names related to the MaxWordsAttribute. The maxwords text appears because you set the rule's ValidationType property to maxwords (and yes, the validation type and all validation parameter names must be lowercase because their values must be legal to use as HTML attribute identifiers).

Now you have metadata on the client, but you still need to write some script code to execute the validation logic.

Custom Validation Script Code

Fortunately, you do not have to write any code that digs out metadata values from data dash attributes on the client. However, you'll need two pieces of script in place for validation to work:

  • The adapter: The adapter works with the unobtrusive MVC extensions to identify the required metadata. The unobtrusive extensions then take care of retrieving the values from data dash attributes, and adapting the data to a format jQuery validation can understand.
  • The validation rule itself: This is called a validator in jQuery parlance.

Both pieces of code can live inside the same script file. Assume for a moment that you want the code to live in the MusicScripts.js file you created in the section “Custom Scripts” earlier in this chapter. In that case, you want to make sure MusicScripts.js appears after the validation scripts appear. Using the scripts section created earlier, you could do this with the following code:

@section scripts
{
   <script src="@Url.Content("∼/Scripts/jquery.validate.min.js")"
           type="text/javascript"></script>
   <script src="@Url.Content("∼/Scripts/jquery.validate.unobtrusive.min.js")"
           type="text/javascript"></script>
   <script src="@Url.Content("∼/Scripts/MusicScripts.js")" type="text/javascript">
   </script> 
}

Inside of MovieScripts.js, some references give you all the IntelliSense you need:

/// <reference path="jquery-1.4.4.js" />
/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

The first piece of code to write is the adapter. The MVC framework's unobtrusive validation extension stores all adapters in the jQuery.validator.unobtrusive.adapters object. The adapters object exposes an API for you to add new adapters, which are shown in Table 8.2.

Table 8.2: Adapter Methods

Name Description
addBool Creates an adapter for a validator rule that is “on” or “off.” The rule requires no additional parameters.
addSingleVal Creates an adapter for a validation rule that needs to retrieve a single parameter value from metadata.
addMinMax Creates an adapter that maps to a set of validation rules—one that checks for a minimum value and one that checks for a maximum value. One or both of the rules may run depending on the data available.
add Creates an adapter that doesn't fit into the preceding categories because it requires additional parameters, or extra setup code.

For the maximum words scenario, you could use either addSingleVal or addMinMax (or add, because it can do anything). Because you do not need to check for a minimum number of words, you can use the addSingleVal API as shown in the following code:

/// <reference path="jquery-1.4.4.js" />
/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

$.validator.unobtrusive.adapters.addSingleVal("maxwords", "wordcount");

The first parameter is the name of the adapter, and must match the ValidationProperty value you set on the server-side rule. The second parameter is the name of the single parameter to retrieve from metadata. Notice you don't use the data- prefix on the parameter name; it matches the name of the parameter you placed into the ValidationParameters collection on the server.

The adapter is relatively simple. Again, the primary goal of an adapter is to identify the metadata that the unobtrusive extensions need to locate. With the adapter in place, you can now write the validator.

The validators all live in the jQuery.validator object. Like the adapters object, the validator object has an API to add new validators. The name of the method is addMethod:

$.validator.addMethod("maxwords", function (value, element, maxwords) {
    if (value) {
        if (value.split(’ ‘).length > maxwords) {
            return false;
        }
    }
    return true;
});

The method takes two parameters:

  • The name of the validator, which by convention matches the name of the adapter (which matches the ValidationType property on the server).
  • A function to invoke when validation occurs.

The validator function accepts three parameters, and can return true (validation passed) or false (validation failed):

  • The first parameter to the function will contain the input value (like the title of an album).
  • The second parameter is the input element containing the value to validate (in case the value itself doesn't provide enough information).
  • The third parameter will contain all the validation parameters in an array, or in this case, the single validation parameter (the maximum number of words).
note
UnFigure

To bring the validation code into your own project, use NuGet to install the Wrox.ProMvc3.Ajax.CustomClientValidation package.

Although the ASP.NET MVC AJAX helpers provide a great deal of functionality, there is an entire ecosystem of jQuery extensions that go much further. The next section explores a select group.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.51.36