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.
Great Post. Good work :)
ReplyDeletenice!!!
ReplyDeleteWhy 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.
ReplyDeleteThis is the same thing i'm searching right now an answer for...did you find anything ?!?
DeleteAfter 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...
I'm afraid i'm looking a bit naive right now :)
DeleteHi Bhushan and Cris,
DeleteRegarding 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!
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.
DeleteWhy would you even expect that?
Deletenice Post. It helps me alot.
ReplyDeletePankaj y.
JS code doesn't work. You have to manually add custom rule to each form field in order to run such custom validation...
ReplyDelete