Monday, November 21, 2011

Validation attribute on nested list item

I'm working on some data annotation validation stuff and today I came to a requirement to validate a type nested property which is a list of string. The requirement is quite simple, just make sure every string in the list must be numeric. NumericAttribute is a custom ValidationAttribute to validate a string and to check whether it contains only numeric. Well this is a very basic example and I'm thinking futher, may be I'll need to validate different object type in the list instead of simple string and we might need different validation logic to apply on them. That leads me to this post:
Let's say I have an object definition like below:

public class Parent
{
    public List<ChildObject> Children {get;set;}
}

I want to have a validation attribute to decorate on the Children property and when the validation process happen, that attribute will validate every single ChildObject item in the list.
The attribute I want is a derived of ValidationAttribute, take different types of other Validation Attributes as the constructor parameters. And certainly, when override method IsValid, it will loop through the validation attributes set in the constructor to validate all items in the list:
public class ValidateItemUsingAttribute : ValidationAttribute
{
    private readonly Type[] _attributeTypes;

    public ValidateItemUsingAttribute(params Type[] attributes)
    {
        Check.Requires<ArgumentNullException>(attributes != null);
        Check.Requires<ArgumentException>(!attributes.Any(x => !typeof(ValidationAttribute).IsAssignableFrom(x)));
        _attributeTypes = attributes;
    }

    public override bool IsValid(object value)
    {
        if (value == null || !(value is IEnumerable) || value is string)
        {
            return true;
        }

        var index = 0;
        foreach(var item in value as IEnumerable)
        {
            foreach (var type in _attributeTypes)
            {
                var validator = Activator.CreateInstance(type) as ValidationAttribute;

                var validationResult = validator.GetValidationResult(item, new ValidationContext(item, null, null));
                if (validationResult != null)
                {
                    ErrorMessage = validationResult.ErrorMessage;
                    MemberName = string.Format("{0}[{1}]", MemberName, index);
                    return false;
                }
            }
            index++;
        }
        return true;
    }
}

Because this attribute uses Activator to create instance of other validation attribute, that require those nested validation attributes to have parameterless constructor. So for example we need to use type of StringLengthAttribute for the constructor, we may implement a derived class of StringLengthAttribute and use the new type for the constructor :D
public class Parent
{
    [ValidateItemUsing(typeof(SomeValidationAttribute), typeof(AnotherValidationAttribute))]
    public List<ChildObject> Children {get;set;}
}

Anyways, I hope this is a good idea. Cheers.

0 comments:

Post a Comment