In Part-1, we have seen how to combine independent validation attributes in a single group, so that we can check more than one validation rule at one time and display custom error message that includes messages of the failed rules from the subset. In this post we will see how to make it work without making round-trip to server using jquery valdiator and unobtrusive jquery validation.

Getting it to work on the client side with Unobtrusive jQuery Validation

For client side validation, we implement IClientValidatable for ValidNameAttribute created in Part-1. Since the GetClientValidationRules method returns IEnumerable, you would think adding client-side validation should be as simple as returning collection of validation rules.

public IEnumerableModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{ 
 yield return _onlyOneEntityValidator.GetClientValidationRules(metadata, context).First(); 
 yield return _cannotContainNumbers.GetClientValidationRules(metadata, context).First(); 
 yield return _cannotBeNotApplicable.GetClientValidationRules(metadata, context).First(); 
}

However, if you run the application you will see the error message is no longer a combination of all the error messages of failed rules in the group. Rather, the client-side validation in this case works similar to when you apply the DataAnnotation validators separately. What happened?

Well, when you apply client-side validation in this manner, the ASP.NET MVC simply adds these rules to the list of client-side validation rules for the property. The data-val attributes created by ASP.NET MVC is similar to data-val attribute created if these rules were applied separately. In FireBug's Html section, you see that data-val-cannotbenotapplicable, data-val-cannotcontainnumbers and data-val-onlyoneentity attributes are applied to FirstName and LastName, where FirstName is decorated with ValidName attribute and LastName has DataAnnotation validators applied separately. The client-side has no information that these validations are part of a group in the case of FirstName.

public class RegisterModel 
 {
 [Required]
 [ValidName] 
 public string FirstName { get; set; }
 
 [Required]
 [OnlyOne]
 [CannotBeNotApplicable]
 [CannotContainNumbers]
 public string LastName { get; set; }
 
 [Email]
 public string Email { get; set;}
 
 }

Dynamically change the error message on the client side

In fact, on the client-side, to display error messages of failed rules from the subset of validation rules, we need to change the validation message dynamically to reflect what the user has currently entered. As the input changes, the message should change reflecting only messages for failed rules, until the input is valid.

We can do this on the client-side using javscript and jQuery validators. To dynamically change the error message, we bind our custom ValidNameAttribute to client-side validation rule named 'validname'. Then add each client validation rule name from the group, the parameters required for these rules, and the error messages for each rule as parameters to the rule's ValidationParameters collection. In our implementation, the validation rule name, parameters requried by the rules and error messages for these rules are matched by indexes on the client-side, so we have added them in the same order.

public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) 
 { 
 //Bind to validname rule on client-side
 var rule = new ModelClientValidationRule()
 {
 ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
 ValidationType = "validname",
 };
 
 //List of client-side validation rules of subset of validators
 var clientvalidationmethods = new Liststring>
 {
 _onlyOneEntityValidator.GetClientValidationRules(metadata,context).First().ValidationType,
 _cannotBeNotApplicable.GetClientValidationRules(metadata,context).First().ValidationType,
 _cannotContainNumbers.GetClientValidationRules(metadata,context).First().ValidationType
 };
 
 //List of paramateres requried by client-side validation rules of subset of validators
 var patterns = new Liststring>
 {
 _onlyOneEntityValidator.Pattern,
 _cannotBeNotApplicable.Pattern,
 _cannotContainNumbers.Pattern,
 };
 
 //Errormessages for each of the validation rules
 var errorMessages = new Liststring>
 {
 _onlyOneEntityValidator.FormatErrorMessage(metadata.GetDisplayName()),
 _cannotBeNotApplicable.FormatErrorMessage(metadata.GetDisplayName()),
 _cannotContainNumbers.FormatErrorMessage(metadata.GetDisplayName())
 };
 
 rule.ValidationParameters.Add("clientvalidationmethods", clientvalidationmethods.ToConcatenatedString());
 rule.ValidationParameters.Add("patterns", patterns.ToConcatenatedString());
 rule.ValidationParameters.Add("errormessages", errorMessages.ToConcatenatedString());
 yield return rule;
 
 }

Add a script to bind a custom validator to a unobtrusive adapter. We pass the array of validation rule names, paramters required by these rules and error messages for these rules as parameters to the validation method named 'validname'. The values in these arrays are matched by indexes. You do not want to set options.messages['validname'], leave it undefined.

$.validator.unobtrusive.adapters.add('validname', ['clientvalidationrules', 'regexpatterns', 'errormessages'], function (options) { 
 options.rules['validname'] = { 
 //The rule names (params['clientvalidationrules']) received from GetClientValidationRules method 
 // is the unobtrusive adapter names for the rules. 
 // if the corresponding jquery validator method name is different from the adapter name, 
 // replace it with jquery validator method name.
 clientvalidationmethods: options.params['clientvalidationrules'].split(','), 
 patterns: options.params['regexpatterns'].split(','), 
 errormessages: options.params['errormessages'].split(',') 
 }; 
});

Add a method to jquery validator that will execute the validation subset of validation rules and create an errormessage string concatenating the result of these validations. Lastly we set $.validator.messages.validname with the dynamically created errormessage which will be displayed as error message.

$.validator.addMethod("validname", function (value, element, param) { 
 if (value == "" || value == null || value == undefined) {
 return true;
 }
 //array of jquery validation rule names
 var validationrules = param["clientvalidationrules"];
 
 //array of paramteres required by rules, in this case regex patterns
 var patterns = param["regexpatterns"];
 
 //array of error messages for each rule
 var rulesErrormessages = param["errormessages"];
 
 var validNameErrorMessage = new Array();
 var index = 0
 
 for (i = 0; i  patterns.length; i++) {
 var valid = true;
 var pattern = patterns[i].trim();
 
 //get a jquery validator method. 
 var rule = $.validator.methods[validationrules[i].trim()];
 
 //create a paramtere object
 var parameter = new Object();
 parameter['pattern'] = pattern;
 
 //execute the rule
 var isValid = rule.call(this, value, element, parameter) && valid;
 
 if (!isValid) {
 //if rule fails, add error message
 validNameErrorMessage[index] = rulesErrormessages[i];
 index++;
 }
 }
 //if we have more than on error message, one of the rule has failed
 if (validNameErrorMessage.length > 0) {
 //update the error message for 'validname' rule
 $.validator.messages.validname = "This field " + validNameErrorMessage.toString();
 return false;
 }
 return true;
}, "This is not a valid individual name"//default error message
);

To set the errormessage dynamically on the client side, it is important to not set options.messages.rulename or set it to undefined while binding your custom validator for unobtrusive validation. But, why?

When you add a method to jquery.validator, the $.validator.messages.rulename holds the errormessage for the rule. In above example, the validname rule error message is set to "This is not a valid individual name"

This is the default error message we passed to jQuery.validator for validname rule when we added the method for validname. We don't want this error message. The exact error message we would like to display is known only when we execute this method for current input and determine which rules from our subset have failed. Once we determine this we update $.validator.messages.validname . This updates error message for validname with new message. The following image from FireBug shows updated message for $.validator.messages.validname after the method is executed.

A jquery.validator.unobtrusvie's onError method runs after the validation fails and this method overrides the validation message of your validation rule with the one set by the unobtrusive adapter. Since we have not defined message in unobtrusive adapter for validaname, the message in jquery validator rule is displayed.

function onError(error, inputElement) { // 'this' is the form element
 var container = $(this).find("[data-valmsg-for='" + inputElement[0].name + "']"),
 replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false;
 
 container.removeClass("field-validation-valid").addClass("field-validation-error");
 error.data("unobtrusiveContainer", container);
 
 if (replace) {
 container.empty();
 error.removeClass("input-validation-error").appendTo(container);
 }
 else {
 error.hide();
 }
 }

Given this approach, the client-side error message changes dynamically as the user types:



We have successfully implemented grouping of multiple validation rules and dynamically gerenate error messages using unobtrusive jquery validation.