Why doesn't automatic attribute validation work for models inheriting collections?

Viewed 366

I want to add model validation via attributes to Web API (.NET Framework 4.7.2), but I ran into the fact that this does not work if the model is a collection.

I have a model class inherited from a dictionary. For example:

...
using System.ComponentModel.DataAnnotations;
...

public class MyModel : Dictionary<string, string>
{
    [StringLength(100)]
    public string SomeField
    {
        get => TryGetValue("someField", out var value) ? value : null;
        set => this["someField"] = value;
    }
}

What I've done:

  1. Added the [ApiController] attribute to the controller class to activate automatic model validation on requests.
  2. Added System.ComponentModel.DataAnnotations to the project.
  3. Added validation attributes to the model fields.

This works for simple model classes, but if the model is inherited from a collection, no attribute validation occurs.

To solve this problem, I implemented the IValidatableObject interface, where I wrote the following:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    const string validationStatusFieldName = "alreadyValidated";
    if (validationContext.Items.TryGetValue(validationStatusFieldName, out var alreadyValidated) && (bool) alreadyValidated)
        return Enumerable.Empty<ValidationResult>();
    validationContext.Items[validationStatusFieldName] = true;

    var validationResults = new List<ValidationResult>();
    var isValid = Validator.TryValidateObject(validationContext.ObjectInstance, validationContext, validationResults, true);
    return isValid
        ? Enumerable.Empty<ValidationResult>()
        : validationResults;
}

In this case, the Validate method is automatically called, where we can manually validate the attributes. However, if the attribute validation passed without validation errors, then the Validate method will be called, which, in this case, will call itself over and over again, which leads to a stack overflow exception. So I added a check to ensure that the validation has already taken place.

Thus it works, but it is not clear: why the attributes are not automatically checked if this model inherits a collection, and how to solve it?

1 Answers

The validator has a condition where he chooses exactly how he will validate the object. The check that the model is a collection happens first, after which each of its elements is checked. And since this collection stores strings on which there are no validating constraints, the validation goes without any comments. And if there are no problems with field validation through attributes, then the Validate method will be called, which is what happens.

How to get around this:

  • implement the IValidatableObject interface, and in the Validate method, try to validate the current instance, remembering to exit the infinite recursion. (was done in the question)

  • create a custom validation attribute for the model class (inherited from ValidationAttribute), which will search for properties with validation attributes and validate them independently.

Related