Custom Unobstrusive Jquery Validation in ASP.Net MVC 3 using DataAnnotationsModelValidatorProvider

Introduction

In a previous post I covered the use of Custom Unobrusive Validation in ASP.Net MVC using the IClientValidatable. The use of this interface may not always be possible, for example if the validation attribute is defined in an assembly that cannot reference the MVC assembly, or such a reference makes no sense. Under these circumstances, there is another approach to take and that is to use adapters to help the MVC assign the client side validation to apply for a given validation attribute.

Server side code

I’ll use the same example as last time, that is a check sum validator for Australian Business and Company Numbers, but in this case I will not be implementing IClientValidatable.

public abstract class CheckSumNumberAttribute : ValidationAttribute{                       

  private string checkSumType;

  protected CheckSumNumberAttribute(string checkSumType){
    this.checkSumType = checkSumType;
  }

  public string CheckSumType{
    get {return checkSumType;}
  }
}

The implementations of two concrete classes AustralianBusinessNumberAttribute and AustralianCompanyNumberAttribute remain the same.

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

Without the IClientValidatble interface, we need to create a “buddy” class of type DataAnnotationsModelValidator<TValidationAttribute> that MVC will use to create the data-val information for a given validator.

public class CheckSumValidator : 
  DataAnnotationsModelValidator<CheckSumNumberAttribute>{
  private string errorMessage;
  private string checkSumType;

  public RelatedDatesValidator(
      ModelMetadata metadata, ControllerContext context, CheckSumNumberAttribute attribute)
    : base(metadata, context, attribute){
     this.errorMessage = attribute.FormatErrorMessage(metadata.DisplayName);
     this.checkSumType = "checksum";
  }

  public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(){
    var rule = new ModelClientValidationRule{
      ErrorMessage = this.errorMessage,
      ValidationType = this.validationType
    };

    rule.ValidationParameters.Add("checksumtype", checkSumType);
    yield return rule;
  }
}

Having defined the adapter class, it is neccesary to register the adapter with the DataAnnotationsModelValidatorProvider for the validation attribute. Global.asax is a good place to do this.

protected void Application_Start(){
    .....
  DataAnnotationsModelValidatorProvider
     .RegisterAdapter(
         typeof(CheckSumNumberAttribute>), 
         typeof(CheckSumValidator));
    .....
}

Client side code

The javascript required for the client side validation also remains the same as per the previous post, but here is it again for completeness.

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));
};

//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);
  }
});

//register the method with the unobtrusive validator library
$.validator.unobtrusive.adapters.addSingleVal('checksum', 'checksumtype', 'xhalent_checksum');

Of course, my caveats about placing this script after the dependant javascript libraries have been loaded still apply.

Conclusion

This post have demonstrated how to use Custom Unobstrusive Jquery Validation in ASP.Net MVC 3 using DataAnnotationsModelValidatorProvider instead of IClientValidatable. This is a suitable solution where it is not possible to decorate an attribute the the IClientValidatable interface or it is inappropriate to reference the MVC library from the assembly the validation attribute is defined in.

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

One Response to Custom Unobstrusive Jquery Validation in ASP.Net MVC 3 using DataAnnotationsModelValidatorProvider

  1. Pingback: Custom Unobstrusive Jquery Validation in ASP.Net MVC 3 | 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