Search code examples
c#validationdata-annotationsasp.net-core-3.1buddy-class

MVC .net core: Validator.TryValidateObject is not validating attributes, defined in a buddy class


We used DB-first approach to generate models in a .NET core application. DataAnnotations were put in a "buddy" metadata class so as to avoid writing in an autogenerated file. When controller calls TryValidateModel, all works well, Name property is required.

public partial class User
{
    public string Name { get; set; }
}

[ModelMetadataType(typeof(UserMetaData))]
public partial class User : IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { }
}

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

On a service layer of the app, we want to implement additional validation, that also checks if objects are valid in regards to data annotations. This is done via Validator.TryValidateObject() which successfully calls Validate method, but disregards data annotations - user is valid, even with an empty name.

TL;DR: MVC (web project) knows how to consider data annotations put in a "buddy" class via ModelMetadataType attribute, service layer project does not.

I thought i have found the answer here, but it seems that TypeDescriptor.AddProviderTransparent does not work for .net core apps.

Any ideas would be greatly appreciated.


Solution

  • I really hoped for a one line solution :)

    I abused ashrafs answer to his own question like so:

    var metadataAttr = typeof(T).GetCustomAttributes(typeof(ModelMetadataTypeAttribute), true).OfType<ModelMetadataTypeAttribute>().FirstOrDefault();
    if (metadataAttr != null)
    {
        var metadataClassProperties = TypeDescriptor.GetProperties(metadataAttr.MetadataType).Cast<PropertyDescriptor>();
        var modelClassProperties = TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>();
    
        var errs =
            from metaProp in metadataClassProperties
            join modelProp in modelClassProperties
            on metaProp.Name equals modelProp.Name
    
            from attribute in metaProp.Attributes.OfType<ValidationAttribute>()
            where !attribute.IsValid(modelProp.GetValue(model))
            select new ValidationResult(attribute.FormatErrorMessage(Reflector.GetPropertyDisplayName<T>(metaProp.Name)), new[] { metaProp.Name });
    
        validationResults.AddRange(errs);
    }