Reputation: 978
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?
Upvotes: 3
Views: 23717
Reputation: 125207
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.
Upvotes: 2
Reputation: 4505
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.
Upvotes: 4
Reputation: 978
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;
}
Upvotes: 21