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(http://www.codeproject.com/KB/validation/MultipleDataAnnotations.aspx) 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(
                 ExpressionHelper.GetExpressionText(expression));
        string Key = html.ViewData.Model.ToString() + "." + element;
        RequiredIfAttribute.countPerField.Remove(Key);
        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();
 if(countPerField==null)
       countPerField = new Dictionary<string, int>(); 
        if (countPerField.ContainsKey(Key))
        {
          count = ++countPerField[Key];
        }
        else
         countPerField.Add(Key, count);



and only keep below part plus passing your new property

yield return new RequiredIfValidationRule
(string.Format(ErrorMessageString,
metadata.GetDisplayName()),requiredFieldValue,Props,Vals,_uniquesuffix);


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) {              
                           errorIndex=index;
                return false;
            }
        });
              var errormessages=$(element).data('valrequiredifmultiple').split('!');
        if (retVal === false) {
            var evalStr = "this.settings.messages." + $(element).attr("name") +
                     ".requiredifmultiple='" + errormessages[errorIndex] + "'";
            eval(evalStr);
        }
        return retVal;
       };

Post a Comment