Custom Unobstrusive Jquery Validation in ASP.Net MVC 3

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

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 ModelClientValidationCheckSumNumberRule is 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, AustralianBusinessNumberAttribute and AustralianCompanyNumberAttribute is 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

kick it on DotNetKicks.com

About these ads
This entry was posted in ASP.Net MVC, jQuery and tagged , , . Bookmark the permalink.

3 Responses to Custom Unobstrusive Jquery Validation in ASP.Net MVC 3

  1. Jeremy Lundy says:

    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” }
    }
    }
    }

  2. Pingback: Custom Unobstrusive Jquery Validation in ASP.Net MVC 3 using DataAnnotationsModelValidatorProvider | XHalent Coding…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s