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 and data-msg-notequalto. And it's the equivalent notequalto 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 the params 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 &quot; 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="{&quot;other&quot;:&quot;#Property&quot;}" 
                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!");
      }
  });