Introduction
This post demonstrates how to add client side unobtrusive validation for custom validation attributes. I will use a real example, which is the client side validation of the Australian Business Number and Australian Company Numbers for registered companies in Australia. These numbers are validated using a checksum algorithm. This post implements custom validation by having validation attributes implement the IClientValidatable interface. This may not always be appropriate or possible, in which case you may want to use the DataAnnotationsModelValidatorProvider, which I have covered here.
- Define the Custom Validation Attribute
- Server Side Validation
- Using the Attributes
- Client Side Validation
- Hooking it up
- Conclusion
Define the Custom Validation Attribute
I’ll define a base CheckSumNumber attribute that each particular instance will derive from. This needs to implement System.Web.Mvc.IClientValidatable and to return the neccesary System.Web.Mvc.ModelClientValidationRule to the Html renderers when required.
Note how the specification of the actual check sum type is passed in to the constructor of the abstract class.
public abstract class CheckSumNumberAttribute : ValidationAttribute, IClientValidatable{ private string checkSumType; protected CheckSumNumberAttribute(string checkSumType){ this.checkSumType = checkSumType; } public string CheckSumType{ get {return checkSumType;} } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context){ yield return new ModelClientValidationCheckSumNumberRule(this.ErrorMessageString, this.CheckSumType); } }The code for
ModelClientValidationCheckSumNumberRuleis shown below:public class ModelClientValidationCheckSumNumberRule : ModelClientValidationRule{ public ModelClientValidationCheckSumNumberRule(string errorMessage, string checkSumType) : base(){ this.ErrorMessage = errorMessage; this.ValidationType = "checksum"; this.ValidationParameters.Add("checksumtype", checkSumType); } }ABN and ACN server side validation
The implementations of the two concrete classes,
AustralianBusinessNumberAttributeandAustralianCompanyNumberAttributeis shown below. The algorithm are publically available from the the Australian Taxation Office or the Australian Business Registrar.public class AustralianBusinessNumberAttribute : CheckSumNumberAttribute{ private static int[] ABN_WEIGHT = { 10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 }; private static Regex ABNRegex = new Regex("\\d{11}"); public AustralianBusinessNumberAttribute() : base("abn") { } public override bool IsValid(object val){ string ABN = val as string; bool valid = false; if (ABN != null){ ABN = ABN.Replace(" ", "").Trim(); } if (string.IsNullOrEmpty(ABN)){ return true; } if (!ABNRegex.IsMatch(ABN)){ return false; } int sum = 0; try{ for (int i = 0; i < ABN_WEIGHT.Length; i++){ // Subtract 1 from the first left digit before multiplying against the weight if (i == 0){ sum = (Convert.ToInt32(ABN.Substring(i, 1)) - 1) * ABN_WEIGHT[i]; }else{ sum += Convert.ToInt32(ABN.Substring(i, 1)) * ABN_WEIGHT[i]; } } valid = (sum % 89 == 0); } catch{ valid = false; } return valid; } } public class AustralianCompanyNumberAttribute : CheckSumNumberAttribute{ private static int[] ACN_WEIGHT = { 8, 7, 6, 5, 4, 3, 2, 1 }; private static Regex ACNRegex = new Regex("\\d{9}"); public AustralianCompanyNumberAttribute(): base("acn"){} public override bool IsValid(object val){ string ACN = val as string; bool valid = false; if (ACN != null){ ACN = ACN.Replace(" ", "").Trim(); } if (string.IsNullOrEmpty(ACN)){ return true; } if (!ACNRegex.IsMatch(ACN)){ return false; } int remainder = 0; int sum = 0; int calculatedCheckDigit = 0; try{ // Sum the multiplication of all the digits and weights for (int i = 0; i < ACN_WEIGHT.Length; i++){ sum += Convert.ToInt32(ACN.Substring(i, 1)) * ACN_WEIGHT[i]; } // Divide by 10 to obtain remainder remainder = sum % 10; // Complement the remainder to 10 calculatedCheckDigit = (10 - remainder == 10) ? 0 : (10 - remainder); // Compare the calculated check digit with the actual check digit valid = (calculatedCheckDigit == Convert.ToInt32(ACN.Substring(8, 1))); } catch{ valid = false; } return valid; } }That's it for the server side programming. If you add these attributes to your data model the model binder will validate accordingly during post back.
Using the attributes
public class CompanyModel{ public string CompanyName{get;set;} [AustralianBusinessNumber(ErrorMessage="The ABN is incorrect")] public string AustralianBusinessNumber{get;set;} [AustralianCompanyNumber(ErrorMessage = "The ACN is incorrect")] public string AustralianCompanyNumber{get;set;} }When the editor for this mdoel is emitted, the follows HTML is generated:
<input data-val="true" data-val-checksum="The ABN is incorrect" data-val-checksum-checksumtype="abn" id="AustralianBusinessNumber" name="AustralianBusinessNumber" type="text" value="" /> <input data-val="true" data-val-checksum="The ACN is incorrect" data-val-checksum-checksumtype="acn" id="AustralianCompanyNumber" name="AustralianCompanyNumber" type="text" value="" />Client side ABN and ACN validation
To enable client side validation, you need to define the validation methods. I have cerate client side version of the check sum validators for ABN and ACN as shown below. Note the use of a local namespace 'Xhalent' to prevent pollution of the global object.
var Xhalent = Xhalent || {}; Xhalent.validateABN = function (value) { value = value.replace(/[ ]+/g, ''); if (!value.match(/\d{11}/)) { return false; } var weighting = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; var tally = (parseInt(value.charAt(0)) - 1) * weighting[0]; for (var i = 1; i < value.length; i++) { tally += (parseInt(value.charAt(i)) * weighting[i]); } return (tally % 89) == 0; }; Xhalent.validateACN = function (value) { value = value.replace(/[ ]+/g, ''); if (!value.match(/\d{9}/)) { return false; } var weighting = [8, 7, 6, 5, 4, 3, 2, 1]; var tally = 0; for (var i = 0; i < weighting.length; i++) { tally += (parseInt(value.charAt(i)) * weighting[i]); } var check = 10 - (tally % 10); check = check == 10 ? 0 : check; return check == parseInt(value.charAt(i)); };Hooking it up
You then need to register these functions with the jquery validation framework. I've added an inline function that switches to correct function implementation based on the check sum type parameter.
//get reference to global jquery validator object and addMethod named 'xhalent_checksum' $.validator.addMethod("xhalent_checksum", function (value, element, checksumtype) { if (value == null || value.length == 0) { return true; } if(checksumtype == 'abn') { return Xhalent.validateABN(value); }else if (checksumtype == 'acn') { return Xhalent.validateACN(value); } });The final step is to wire up the adapter for the unobtrusive validation library to the now-registered validation method. The method I have used takes three parameters - the name of the rule, the name of the required attribute for the rule, and the jquery validation method to call to process the rule. There are other methods for adding adapters - for a validator that has no parameters the best method to use is $.validator.unobtrusive.adapters.addBool(adaptername, rulename).
$.validator.unobtrusive.adapters.addSingleVal('checksum', 'checksumtype', 'xhalent_checksum');It's important that any javascript you have written is loaded after the libraries on which they depend has bene loaded. For that reason, I tend to incoporate them into a separate .js file, which can then be minified.
Conclusion
This post has demonstrated how to implement client side unobstrusive validation for custom validators in ASP.net MVC 3, and shown implementations for the validation of the Australian Business Number and Australian Company Number

Any idea how I can get the unobtrusive adapter to wireup the “depends” validation rule to make a validation conditional? I already have the server-side code working with a custom ValidationAttribute.
This is the equivalent of what I want on the client side:
rules: {
MyInputField: {
required: {
depends: function () { return $(“#DependentField”).val() == “DependentValue” }
}
}
}
I’ve covered custom client validation here. Hope this gives you a few ideas.
Pingback: Custom Unobstrusive Jquery Validation in ASP.Net MVC 3 using DataAnnotationsModelValidatorProvider | XHalent Coding…