Unobtrusive Client and Server Side Not Equal To Validation in MVC using Custom Data Annotations

By | February 25, 2014

Yesterday we discussed about Unobtrusive Client and Server Side Age Validation now let’s do a not equal to validator which is basically the opposite of the Compare validator. 

So where can this be useful?

If you don’t want two items to have the same value then this is your solution.  Lets say you don’t want to have same answers on two different text boxes or you don’t want two same selections on dropdowns.  One practical application is in Secret Question forms on sign up like the one below

01 Form

Now lets start, first lets create your own custom validation, let us call it UnlikeAttribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttributeIClientValidatable
{
    private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";

    public string OtherProperty { getprivate set; }
    public string OtherPropertyName { getprivate set; }

    public UnlikeAttribute(
        string otherProperty,
        string otherPropertyName)
        : base(DefaultErrorMessage)
    {
        OtherProperty = otherProperty;
        OtherPropertyName = otherPropertyName;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty);

            var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);

            if (value.Equals(otherPropertyValue))
            {
                return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            }
        }

        return ValidationResult.Success;

    }
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ValidationType = "unlike",
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
        };

        rule.ValidationParameters.Add("otherproperty", OtherProperty);
        rule.ValidationParameters.Add("otherpropertyname", OtherPropertyName);

        yield return rule;
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(ErrorMessageString, name, OtherPropertyName);
    }
}

In this class you notice you have three methods, first is IsValid.   We override this method from the ValidationAttribute and this basically validates the specified value with respect to the current validation attribute, it outputs an instance of the System.ComponentModel.DataAnnotations.ValidationResult class.  The next method is FormatErrorMessage which is from the same ValidationAttribute overriden class, as the name suggests this gets or sets an error message to associate with a validation control if validation.  Now we have GetClientValidationRules which come from IClientValidatable where it provides a way for the ASP.NET MVC validation framework to discover at run time whether a validator has support for client validation.

Now let use this in your view model

Lets say your Secret Question Field day field is called SecretQuestion1Id and SecretQuestion2Id, we want it to be required and not to contain the same value

[DisplayName("First Secret Question")]
[Required]
public int SecretQuestion1Id { getset; }

[DisplayName("Second Secret Question")]
[Required]
[Unlike("SecretQuestion1Id""First Secret Question")]
public int SecretQuestion2Id { getset; }

Now if  you notice you have 2 parameters in
Unlike, first is SecretQuestion1Id, this is where you compare this element with.  Second is just defines the name of the element this is compared with.  They are then mapped to otherProperty and otherPropertyName of your UnlikeAttribute class.  You will also notice we use Unlike and not UnlikeAttribute, you can use both as it auto maps with the class.

At this point your server-side validation will now work all you need to do is before doing anything check the ModelState in your controller like this

[AcceptVerbs(HttpVerbs.Post)]
[Transaction]
public ActionResult CreateAccount(RegisterPageViewModel viewModel)
{
    var errorMessage = string.Empty;

    if (ModelState.IsValid)
    {
        var newExtendedUserDetails = activeDirectoryTasks.CreateUser(
            viewModel.FirstName,
            viewModel.LastName,
            viewModel.Email,
            viewModel.BirthDate.Value,
            viewModel.SecretQuestion1Id,
            viewModel.SecretQuestionAnswer1,
            viewModel.SecretQuestion2Id,
            viewModel.SecretQuestionAnswer2,
            out errorMessage
            );

        if (errorMessage == string.Empty)
        {
            //Success
            viewModel = registerQuery.GetSuccessPageViewModel(viewModel);
            return View("Success", viewModel);
        }
        //Error on Save
        viewModel = registerQuery.GetErrorPageViewModel(viewModel);
        return View("Error", viewModel);
    }
    //Validation Error
    viewModel = registerQuery.GetRegisterPageViewModel(viewModel);
    return View("Index", viewModel);
}

So if you turn off your JavaScript and run then it should throw a validation error like such.

02 Server Error

Now lets enable your Unobtrusive Validation,  first is you need to create a separate js file.  I called mine custom-validators.js and I include all my custom validators there.  Inside that fill would be something like this

// The unlike function
$.validator.addMethod(
    'unlike',
   function (value, element, params) {
       console.debug(element);
        if (!this.optional(element)) {
            var otherProperty = $('#' + params.otherproperty)
            return (otherProperty.val() != value);
        }
        return true;
    });

$.validator.unobtrusive.adapters.add(
    'unlike', ['otherproperty''otherpropertyname'], function (options) {
        var params = {
            otherproperty: options.params.otherproperty,
            otherpropertyname: options.params.otherpropertyname
        };
        options.rules['unlike'] = params;
        options.messages['unlike'] = options.message;
    });

First function just adds the method and the second adds the unobtrusive adapter.  Take note of the names as this is the one you declared early on your ModelClientValidationRule on the UnlikeAttribute.

Next is to make sure you reference to the following JavaScript in this specific order.

<script src="@Url.Content("~/Scripts/jquery-2.0.3.min.js")" type="text/javascript"></script>
<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/custom-validators.js")" type="text/javascript"></script>

And if you are using Bundle Collections then it will be like this

bundles.Add(new ScriptBundle("~/bundles/jquery")
    .Include("~/scripts/jquery-2.0.3.min.js")
    .Include("~/scripts/jquery.validate.min.js")
    .Include("~/scripts/jquery.validate.unobtrusive.min.js")
    .Include("~/scripts/custom-validators.js")
    );

and if you’re using Bundle Collection clear the default ordering as it will sort it alphabetically, you can do so by calling FileSetOrderList.Clear() after all the bundles have been created.

bundles.FileSetOrderList.Clear();

Dont also forget to put this on your web.config

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

Now lets see if everything works fine, code behind should show your data-val’s created from your attribute

03 Validations Showing

And it should do its validation

04 Client Validation

Recommended

One thought on “Unobtrusive Client and Server Side Not Equal To Validation in MVC using Custom Data Annotations

  1. Pingback: How to compare a model property for No Equality with multiple fields in Model in MVC | FYTRO SPORTS

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.