Custom Validation Demo
It is also possible to extend jQuery Validation with custom attributes. There's a good notequalto
example of how this is done using jquery.validate.unobtrusive.js on Stack Overflow. For this demo I'm planning to demonstrate how the same validation would be implemented using jQuery Validation Unobtrusive Native.
Credit and thanks to Darin Dimitrov for his original example and thanks to the Stack Overflow folks for letting me share this.
This is the NotEqualToAttribute
class. As you can see this implements the IClientValidatable
interface which amounts to just the GetClientValidationRules
method. And it is this method, and this method alone, that is relevant for jQuery Validation Unobtrusive Native just as it is for jquery.validate.unobtrusive.js.
To go to a granualar level you can map each property of the ModelClientValidationRule
to jQuery Validation as follows:
ValidationType
is the jQuery Validation method or rule name. The value that this is set to will lead to the naming of data attributes like (in this example)data-rule-notequalto
anddata-msg-notequalto
. And it's the equivalentnotequalto
method that we'll need to add to jQuery Validation to get our client side implementation working.ErrorMessage
is used to create the content of the (again, in this example)data-msg-notequalto
attribute. This is the custom error message for this validation.- The
ValidationParameters
Dictionary ends up being passed through to theparams
parameter in our client side implementation as a JavaScript Object Literal (through the magic of JSON). You can see how this is used in the JavaScript.
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Web.Mvc; namespace jQuery.Validation.Unobtrusive.Native.Demos.CustomAttributes { public class NotEqualToAttribute : ValidationAttribute, IClientValidatable { public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule { ErrorMessage = ErrorMessage, ValidationType = "notequalto" }; rule.ValidationParameters["other"] = "#" + OtherProperty; // CSS Selector (won't work if applied to nested properties on a viewmodel) yield return rule; } public string OtherProperty { get; private set; } public NotEqualToAttribute(string otherProperty) { OtherProperty = otherProperty; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var property = validationContext.ObjectType.GetProperty(OtherProperty); if (property == null) { return new ValidationResult( string.Format( CultureInfo.CurrentCulture, "{0} is unknown property", OtherProperty ), new [] { validationContext.MemberName } ); } var otherValue = property.GetValue(validationContext.ObjectInstance, null); if (value == otherValue) { return new ValidationResult( FormatErrorMessage(validationContext.DisplayName), new [] { validationContext.MemberName }); } return ValidationResult.Success; } } }
Here's the model, note that the custom NotEqualTo
attribute decorates the DifferentProperty
property on the model:
using System.ComponentModel.DataAnnotations; namespace jQuery.Validation.Unobtrusive.Native.Demos.Models { public class CustomValidationModel { [Required] public string Property { get; set; } [Required, NotEqualTo("Property", ErrorMessage = "These fields cannot match")] public string DifferentProperty { get; set; } } }
Here's the view (which uses the model):
@model jQuery.Validation.Unobtrusive.Native.Demos.Models.CustomValidationModel @using (Html.BeginForm()) { <div class="row"> @Html.LabelFor(x => x.Property, "Put something here:") @Html.TextBoxFor(x => x.Property, true) </div> <div class="row"> @Html.LabelFor(x => x.DifferentProperty, "Put something different here:") @Html.TextBoxFor(x => x.DifferentProperty, true) </div> <div class="row"> <button type="submit" class="btn btn-default">Submit</button> <button type="reset" class="btn btn-info">Reset</button> </div> }
Here's the HTML that the view generates. If you look closely at the data-rule-notequalto
attribute you can see the JSON hiding in plain sight in the form of many a "
standing in for a "
. Here's the more human readable version: {"other":"#Property"}
. And you can see that this single object represents the serialization of the ValidationParameters
Dictionary on the attribute; an object with a single property.
<form action="/Demo/CustomValidation" method="post"> <div class="row"> <label for="Property">Put something here:</label> <input data-msg-required="The Property field is required." data-rule-required="true" id="Property" name="Property" type="text" value="" /> </div> <div class="row"> <label for="DifferentProperty">Put something different here:</label> <input data-msg-notequalto="These fields cannot match" data-msg-required="The DifferentProperty field is required." data-rule-notequalto="{"other":"#Property"}" data-rule-required="true" id="DifferentProperty" name="DifferentProperty" type="text" value="" /> </div> <div class="row"> <button type="submit" class="btn btn-default">Submit</button> <button type="reset" class="btn btn-info">Reset</button> </div> </form>
Here's the JavaScript that adds our custom rule / method and initialises the validation.
The name of the custom rule / method is the same as the ValidationType
specified on the attribute, ie "notequalto"
. The "These shouldn't equal"
string is the default error message that will be displayed if an ErrorMessage
wasn't specified when the attribute was being applied. And if you look at the params
parameter you can see this is the client side equivalent of our ValidationParameters
Dictionary on the attribute. As you can see, we use the params.other
property to look up our comparison element.
// Add our custom rule / method jQuery.validator.addMethod("notequalto", function (value, element, params) { return this.optional(element) || value !== $(params.other).val(); }, "These shouldn't equal"); $("form").validate({ submitHandler: function (form) { alert("This is a valid form!"); } });