Can't change Metadata after Validation

190 Views Asked by At

I'm trying to implement a validation with attributes that I can change on runtime using AssociatedMetadataTypeTypeDescriptionProvider and TypeDescriptor.AddProvider.

It works fine if it is set only once, but if you want to change provider after validation using Validator.ValidateObject or Entity Framework (it has automatic entity validation on SaveChanges) it will suddenly stuck on the provider you have set and there is no way to change it.

Lets assume that we have this class with two metadata's:

public class Person
{
    public class CreateMetadata
    {   
        [Required]
        public string Name { get; set; }
    }
        
    public class UpdateMetadata : CreateMetadata
    {
        [Required]
        public string Password { get; set; }
    }

    public string Name { get; set; }
    public string Password { get; set; }
}

Create person and metadata instances:

Person person = new Person();

TypeDescriptionProvider createMetaProvider;
TypeDescriptionProvider updateMetaProvider;
            
createMetaProvider = new AssociatedMetadataTypeTypeDescriptionProvider(
    type: typeof(Person), 
    associatedMetadataType: typeof(Person.CreateMetadata));

updateMetaProvider = new AssociatedMetadataTypeTypeDescriptionProvider(
    type: typeof(Person),
    associatedMetadataType: typeof(Person.UpdateMetadata));

Lets validate person first using Create metadata, then Update:

Validate(person, createMetaProvider);
Validate(person, updateMetaProvider);
static void Validate(object obj, TypeDescriptionProvider metadataProvider)
{
    TypeDescriptor.AddProvider(metadataProvider, typeof(Person));

    var context = new ValidationContext(obj);
    var validationResults = new List<ValidationResult>();

    Validator.TryValidateObject(obj, context, validationResults);

    string errors = string
        .Join("\n", validationResults
        .Select(x => x.ErrorMessage));

    Console.WriteLine($"{errors}\n");

    TypeDescriptor.RemoveProvider(metadataProvider, typeof(Person));
}

Output:

The Name field is required.

The Name field is required.

As you may notice, it uses Create metadata in both cases. Lets swap them:

Validate(person, updateMetaProvider);
Validate(person, createMetaProvider);

Output:

The Name field is required.
The Password field is required.

The Name field is required.
The Password field is required.

The same result. First provider is used in both cases.

As I mentioned before, this also happens with Entity Framework after saving changes.

The only solution I found is using custom validation, but that can't be used with EF.
Though it is possible to disable automatic validation in EF, I consider this more as hack than a solution.

Here's my validation implementation:

static void Validate(object obj, TypeDescriptionProvider metadataProvider)
{
    TypeDescriptor.AddProvider(metadataProvider, typeof(Person));

    var sb = new StringBuilder();
    foreach(PropertyDescriptor property in TypeDescriptor.GetProperties(obj))
    {
        foreach(ValidationAttribute attribute in property.Attributes.OfType<ValidationAttribute>())
        {
            if (attribute.IsValid(property.GetValue(obj)))
                continue;

            sb.AppendLine(attribute.FormatErrorMessage(property.Name));
        }
    }
    Console.WriteLine($"{sb}");

    TypeDescriptor.RemoveProvider(metadataProvider, typeof(Person));
}

Usage:

Validate(person, updateMetaProvider);
Validate(person, createMetaProvider);

Output:

The Name field is required.

The Name field is required.
The Password field is required.

Full Source Code

UPDATE 1: Richard Deeming found that validator caches attributes internally, which explains described behaviour. ValidationAttributeStore

0

There are 0 best solutions below