Wednesday, 18 January 2012

How to use same Custom Validation Attribute mutiple times on a property with client side unobtrusive validation in mvc

A week ago, I had a task about checking one date field [field1] with another date field [field2] and make sure that field1 is earlier than field2, we already have had datecompare custom validation attribute and it was supposed to be an easy one and take a tick to add this validation!
When I looked at Model ,I noticed that field1 already has datecompare attribute and it has been checked with other field [field3] and it checks field1 is after field3.
So I decided to set AllowMultiple = true in attribute usage of my customattribute(datecompareattribute) and override TypeId

AllowMultiple = true in AttributeUsage

Allow Multiple Gets or sets a Boolean value indicating  whether more than one instance of the indicated attribute can be specified for a single program element.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]

Override TypeId in attribute

private object _typeId = new object();
public override object TypeId
       get { return _typeId; }

I said to myself how smart I am ;) but after running project I got this bug

Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once

[InvalidOperationException: Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: ....]

with a quick research I found a very impressive post( about it. Thanks to the author for sharing that. Everything is fine and you can use it freely. except three issues which it has and I’m describing now.

1- It only can accept 26 same attribute on a field.
It adds ‘a’ to ‘z’ at the end of attribute name to make it unique. I think it is alright but if you are crazy enough to have more than 26 same attribute on a field so you need to change a way to create unique id for each attribute, go for it and then share your way for us :D

2-Remove static when you defining countPerField!
//To avoid multiple rules with same name
    public static Dictionary<string, int> countPerField = null;
 Special thanks to one of our tech leads who found this!

comment out below part in htmlhelper

string element = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(
        string Key = html.ViewData.Model.ToString() + "." + element;
        if (RequiredIfAttribute.countPerField.Count == 0)
            RequiredIfAttribute.countPerField = null;

adding an extra property (uniquesuffix) in your attribute instead of generating unique id so remove this part to

int count = 0;
string Key =
  metadata.ContainerType.FullName + "." + metadata.GetDisplayName();
       countPerField = new Dictionary<string, int>(); 
        if (countPerField.ContainsKey(Key))
          count = ++countPerField[Key];
         countPerField.Add(Key, count);

and only keep below part plus passing your new property

yield return new RequiredIfValidationRule

and set unique id with help of your new property

//string tmp = count == 0 ? "" : Char.ConvertFromUtf32(96 + count);
        ErrorMessage = errorMessage;
      //  ValidationType = "requiredif"+tmp;
        ValidationType = "requiredif"+uniquesuffix

3- Last one is the way to show proper error message.
You don't need to add extra property to keep error messages

var reqIfMultipleValidator = function (value, element, params) {
        var others = params.others.split('!');
        var reqVals = params.reqval.split('!');
        var msgs = params.errorMsgs.split('!');
        var errMsg = "";

        var values = null;
        if (params.values + "" != "")
            values = params.values.split('!')

        var retVal = true;
        var errorIndex=0;
        $.each(others, function (index, val) {

            var myParams = { "others": val, "reqval": reqVals[index],
                             "values": values[index] };
            retVal = reqIfValidator(value, element, myParams);
           if (retVal === false) {              
                return false;
              var errormessages=$(element).data('valrequiredifmultiple').split('!');
        if (retVal === false) {
            var evalStr = "this.settings.messages." + $(element).attr("name") +
                     ".requiredifmultiple='" + errormessages[errorIndex] + "'";
        return retVal;

Post a Comment