Search code examples
c#validationdata-annotations

Validating lists in C#


We're using DataAnnotations to validate our model.

A very simplified version of our model is:

public class Model
{
    public List<Thing> Things;
}

public class Thing
{
    [Required]
    public string Name {get;set;}
}

Now, the funny thing is that if I create a Thing with no name and add it to the model, I would expect validation to fail, but it passes (shock horror!).

var model = new Model ();
var invalidThing = new Thing (); // No name would fail validation
model.Things.Add(invalidThing );

var validationContext = new ValidationContext(model);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, validationContext, validationResults, true);

Assert.False (isValid);  // This fails!

I think the reason for this is that when you validate the model, it validates each property but not items in the property if it's a collection. Things is a property that has no validation, so it passes (despite the fact that it contains invalid item).

How can we ensure that validation also validates items in collection properties? Is there some out-of-the-box validator I could use?


Solution

  • I have fixed this by creating a custom validator for collections that checks validation on each item. A simplified code would look like this:

    public class ValidateEachItemAttribute : ValidationAttribute
    {
        protected readonly List<ValidationResult> validationResults = new List<ValidationResult>();
    
        public override bool IsValid(object value)
        {
            var list = value as IEnumerable;
            if (list == null) return true;
    
            var isValid = true;
    
            foreach (var item in list)
            {
                var validationContext = new ValidationContext(item);
                var isItemValid = Validator.TryValidateObject(item, validationContext, validationResults, true);
                isValid &= isItemValid;
            }
            return isValid;
        }
    
        // I have ommitted error message formatting
    }
    

    Now decorating the model this way would work as expected:

    public class Model
    {
        [ValidateEachItem]
        public List<Thing> Things;
    }