JQuery unobtrusive validation is convention based attributes decorations on user control elements that are later processed by JQuery. The below code snippet shows the html that is output by the simple validation sample we will be using in the project. There are three elements in this html, a submit button, a span that is placeholder for the validation error message, and an input that has a bunch of data-val-* attributes. The data-val-* attributes are what the JQuery unobtrusive validation interrogates and uses to infer client side validation behavior for controls. This mechanism provides the benefit of not embedding javascript in your page, but also allowing the browser to continue to function if javascript is not enabled. By default there are a number of data-val-* constructs that are supported. For instance, support for data-val-required is provided in jquery.validate.unobtrusive.js.
<form action="/" method="post"> <div> <input class="text-box single-line" data-val="true" data-val-number="The field FavoriteNumber must be a number." data-val-onenum="That&#39;s not your favorite number!" data-val-onenum-thenum="7" data-val-required="The FavoriteNumber field is required." id="FavoriteNumber" name="FavoriteNumber" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="FavoriteNumber" data-valmsg-replace="true"/> </div> <input type="submit" value="submit!" /> </form>
<script type="text/javascript"> // the validation method that will be executed when // javascript is enabled and the element has a decorated // attribute data-val-onenumval $.validator.addMethod("sample_onenumval", function(value, element, theNumber) { if (value == null || value.length == 0) { return true; } if (theNumber == value) { return true; } else { return false; } }); // 1. the first param is the name of the data-val // attribute. used to find controls that // require validation attribute would be // data-val-simple_validation // 2. The second param is the name of the param // attribute for simple_validation. found by // attribute matching a name of // data-val-onenum-thenum // 3. Parameter 3 is optional, and is the name of // the validation method. I intentionally gave // a different name here, to just point out the // name can be different. If value is left off // then the rule name is used to lookup the // validation method $.validator.unobtrusive.adapters.addSingleVal('onenum', 'thenum', 'sample_onenumval'); </script>
The line above that invokes addMethod, contains a lookup key that is used to find the anonymous method that is specified in parameter 2. The validation function will return true if the validation succeeds, or false if validation fails. You can see that the anonymous function takes three arguments…. More on that in a bit. Moving a bit further down you will find a line that adds an entry to an adapters collection. This call tells jquery validation a couple things. First, it states what the lookup key is for data-val-onenum attributes (in this case sample_onenumval). It also tells the adapter what parameter value should be passed to the validation method, in. his case data-val-onenum-thenum. But how in the world!!! (At least that is what I said). It turns out that “adapters” are actually an abstract concept, that you can provide your own implementation for. The addSingleVal adapter provides logic for finding the parameter value and knows how to call the validation function providing said parameter value. When the time codes to validate, JQuery validation defers to the adapter which then interrogates the html to properly build up the function invocation. JQuery unobtrusive validation ships with a default set of adapters that we can use thankfully, so you only need to write one if your needs are not met. I came across an article from Brad Wilson’s blog and he has defined them all extremely well http://bradwilson.typepad.com/blog/2010/10/mvc3-unobtrusive-validation.html. Now onto some C# source code!
public class OneNumberValidationAttribute : ValidationAttribute, IClientValidatable { private int _theNumber; public override bool IsValid(object value) { var isValid = true; if (value != null) { try { var converted = Convert.ToInt32(value); isValid = (converted == _theNumber); } catch (InvalidCastException) { } } return isValid; } public OneNumberValidationAttribute(int theNumber) { _theNumber = theNumber; } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { yield return new OneNumberValidationRule(ErrorMessage, _theNumber); } } public class OneNumberValidationRule : ModelClientValidationRule { private static string _thenumValidationType = "onenum"; private static string _thenumValidationParameterKey = "thenum"; public OneNumberValidationRule(string errorMessage, int theNumber) { base.ErrorMessage = errorMessage; base.ValidationType = _thenumValidationType; base.ValidationParameters.Add(_thenumValidationParameterKey, theNumber); } }
The code above is the definition of the server side validation attribute (specified by implementing the ValidationAttribute class). You will also notice though that the class also implements something called IClientValidatable, which forces the contract of a method class GetClientValidationRules (http://msdn.microsoft.com/en-us/library/system.web.mvc.modelclientvalidationrule.aspx). These rules are serialized as JSON to the client and are what create the unique data-val attribute names that end up decorating the elements. In the code above you will notice that we return a single OneNumberValidationRule that is composed of an error message, a validation type, and a single validation parameter. Also notice that the type matches the name of our validation adapter specified in the javascript. The validation type is what must match the name of the adapter that processes the validation type. In addition, the including of a validation parameter will also add an attribute of data-val-[type]-[paramx] to the element. The attribute decorated view model is below, along with the source code for the project (Source: http://tinyurl.com/3d6vn7w)
public class IndexViewModel { [OneNumberValidationAttribute(7, ErrorMessage="That's not your favorite number!")] public int FavoriteNumber { get; set; } }
As you look at the project you may notice I used the concept of a ViewModel instead of a model for the sample. For all but smaller projects I advocate for following an approach of a single ViewModel to View concept. With all the declarative programming that is supported out of the box by ASP.NET MVC it feels funny to decorate a model object with something that has direct correlation with the UI. I realize this can create more mapping logic in your code, but I believe it creates a cleaner business layer and allows people working on the backend to not worry that they directly affecting the UI.