Enabling Client Side Validation on Custom Data Annotations with IClientValidatable in MVC

In this post you will learn how to create or enable client side validation on the custom data annotations. If you go through my last blog, you will learn how to create custom data annotations to enable server side validation, I also recorded a video for this and uploaded on YouTube. This blog will take you further, so go and read or watch video, if you missed.


So, in the last blog post I talked about creating custom data annotations and created one demo custom annotation by name [ExcludeChar(“/.,!@#$%”)], this annotation helps us to exclude all the characters typed as a parameter (/.,!@#$%) from Name field/property and on validation fail displayed the friendly message ‘Name contains invalid character.’.

Well, let’s look at this GIF to understand how it worked.


You can see, when user clicks on button then it displayed the message ‘Name contains invalid character.’, so this is doing an unnecessary server round-trip just to validate Name. We can tweak and stop this server round-trip, just by few quick codes.

Again, look at this GIF, this is what I wanted to create.


You can see, it works and displays the message ‘Name contains invalid character.’ immediately on typing character which is defined with data annotation. So nice.

I hope you have working server-side validation sample here, if not read blog or watch video (links given above) and create one before proceeding here.

Let’s go step by step from here.

Step 1: Adding IClientValidatable interface and its GetClientValidationRules method

This is exactly the same code I used in last post, I made few additions here which is highlighted.

class ExcludeCharAttribute : ValidationAttribute, IClientValidatable
{
    private readonly string _chars;

    public ExcludeCharAttribute(string chars)
        : base("{0} contains invalid character.")
    {
        _chars = chars;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            var valueAsString = value.ToString();
            for (int i = 0; i < _chars.Length; i++)
            {
                if (valueAsString.Contains(_chars[i]))
                {
                    var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                    return new ValidationResult(errorMessage);
                }
            }
        }
        return ValidationResult.Success;
    }
    //new method
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
        rule.ValidationParameters.Add("chars", _chars);
        rule.ValidationType = "exclude";
        yield return rule;
    }
}

In above code, first derived the ExcludeCharAttribute class from base class ValidationAttribute (in short, which allows server-side validation) and IClientValidatable (to enable client-side validation).

Second, I added a new method GetClientValidationRule method. Actually, IClientValidatable interface defines a single method GetClientValidationRules. When the MVC framework finds a validation object with this interface present, it invokes GetClientValidationRules to retrieve sequence of ModelClientValidationRule objects. These objects carry the metadata, or the rules, the framework sends to the client.

In order to run the validation on client side we need few rules to return like, what error message to display when validation fails, which character(s) to exclude/not accept with Name and an identifier for a piece of JavaScript code that can exclude the words. So, this is what GetClientValidationRules method returning.

Step 2: Verity the appearance of data-val attributes in markup

If you run the application here, you will see following data-val attributes added-up in the markup.


But this is not enough to start client-side validations in action because now we have metadata on the client, but we still need to write some quick script code to dig out metadata values from data-val attributes on the client and execute the validation logic.

Step 3: Add JavaScript code in a new .js file to execute on client-side

Just go and add a new JavaScript file in ‘Scripts’ folder by name ‘excludechar.js’ and use following codes.

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

$.validator.unobtrusive.adapters.addSingleVal("exclude", "chars");

$.validator.addMethod("exclude", function (value, element, exclude) {
    if (value) {
        for (var i = 0; i < exclude.length; i++) {
            if (jQuery.inArray(exclude[i], value) != -1) {
                return false;
            }
        }
    }
    return true;
});

First two lines of code will do nothing but allow the benefits of IntelliSense while writing JavaScript. Alternatively, we could add these references to _references.js file.

The code which is highlighted is the master peace code, adapter. MVC unobtrusive validation stores all adapters in the jQuery.validator.unobtrusive.adapters object, this exposes an API for us to use in application.

I am using addSingleVal adapter method which retrieves a single parameter value from metadata. The first parameter is the name of the adapter and it must match the ValidationProperty value we set on the server-side rule. The second parameter is the name of the single parameter to retrieve from metadata.

Like the adapters object, the validator object has an API to add new validators. The name of the method is addMethod which takes two parameters, first, name of the validator and by convention it matches the name of the adapter and, second, function to invoke when validation occurs. The rest code in the addMethod does nothing but loops through all the excludable characters (/.,!@#$%) and checks its existence in ‘value’ which is nothing but typed Name in the textbox.

Step 4: Adding reference on Create.cshtml and Edit.cshtml Views

If you done with above all, just go and add the reference of JavaScript file on Views, as given below.

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/Scripts/excludechar.js")
}

Keep it in mind, excludechar.js file which we have created should appear at the bottom and after jquery.validate.min.js and jquery.validate.unobtrusive.min.js files.

Now run the application and enjoy client-side validation in action.


Hope this helps.

Comments

  1. Great Post. Good work :)

    ReplyDelete
  2. Why do I need to write the same logic twice, once for server-side validation (in C#), and once for client-side validation (in JavaScript)? I would expect MVC to generate JavaScript itself.

    ReplyDelete
    Replies
    1. This is the same thing i'm searching right now an answer for...did you find anything ?!?

      After creating server-side validation with DataAnnotations custom attributes on the model's fields, I was expecting that MVC will generate it's scripts so that the same validations will be made client-side without me having to write the same logic twice...

      Delete
    2. I'm afraid i'm looking a bit naive right now :)

      Delete
    3. Hi Bhushan and Cris,

      Regarding custom validations, MVC framework only support the client-side validations generator for predefined validations like Range validator on string, Required fields and so on. But when you define your own rules to validate some fields then MVC could not use your rules for generate the client-side validations i.e. JS code.
      Therefore, you have to separately write code for client-side in case of custom validations.
      Hope it helps!

      Delete
    4. That would be wonderful if Visual Studio could do that for us, but unfortunately there is currently no auto-conversion of C# to javascript validation code. Duplicating the validation logic for any custom requirements (apart from the standard stuff like Required, allowed ranges, etc., which jquery validation will handle) between client and server is an ugly requirement for web-programming.

      Delete
    5. Why would you even expect that?

      Delete
  3. nice Post. It helps me alot.
    Pankaj y.

    ReplyDelete
  4. JS code doesn't work. You have to manually add custom rule to each form field in order to run such custom validation...

    ReplyDelete

Post a Comment

Popular posts from this blog

Migrating database from ASP.NET Identity to ASP.NET Core Identity

Customize User's Profile in ASP.NET Identity System