简体   繁体   中英

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?

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

Another alternative, if this is ASP.NET MVC , would be to implement IValidatableObject in your model. Like:

public class Model: IValidatableObject
{
    public List<Thing> Things;

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
       //your validation logic here
    }
}

Then the result of ModelState.IsValid in your controller will depend on that implementation of Validate method. This is useful when multiple properties of your model are dependent on each other.

The default behavior in your question is not surprising, let's describe it. Let's say you have a property of type Dictionary<string, Thing> or a property of type Something<Thing> or an untyped collection containing Thing objects in the Model , then how could we expect the Validator perform a validation against Thing objects which are stored in those properties?

We cannot expect Validator to perform a validation against Thing objects which are stored in those properties, because it doesn't have any information about how it should validate those properties. To validate a property, Validator looks for ValidationAttribute for that property and since it doesn't find any validation attribute, it doesn't validate that property.

As a result, you need to create some ValidationAttribute to do that for you and decorate properties with validation attributes. You can implement something like what you implemented in your answer.

Note: In context of ASP.NET MVC you don't need to be worried about it. Default model binder takes care of all validation attributes when model binding, even when model binding to a list.

It's what the default model binder does. When creating each element, when assigning values to properties, it validates property and add the validation errors to model state. At last, all properties and objects are validated and model state contains all validation errors.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM