验证后无法修改Metadata:动态元数据提供器切换失效问题
运行时切换数据注解验证特性失效问题
问题描述
尝试通过AssociatedMetadataTypeTypeDescriptionProvider和TypeDescriptor.AddProvider实现运行时可切换验证特性的属性校验,仅单次设置时运行正常,但若调用Validator.ValidateObject完成验证,或是使用Entity Framework(执行SaveChanges时会触发自动实体验证)后再切换元数据提供器,程序会固定使用首次配置的提供器,无法再变更。
复现步骤
1. 定义实体与两套元数据类
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; } }
2. 初始化实体与元数据提供器
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));
3. 调用默认验证方法
// 先使用Create元数据验证,再使用Update元数据验证 Validate(person, createMetaProvider); Validate(person, updateMetaProvider); // Validate方法实现 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)); }
运行结果
首次调用顺序为Create验证在前、Update验证在后时,输出如下:
The Name field is required. The Name field is required.
两次验证均使用了Create元数据规则。
调换调用顺序,先执行Update验证再执行Create验证时,输出如下:
The Name field is required. The Password field is required. The Name field is required. The Password field is required.
两次验证均使用了首次调用的Update元数据规则,该问题在Entity Framework执行SaveChanges触发自动校验后也会出现。
现有临时方案
目前可通过自定义遍历属性的验证逻辑实现规则切换,代码如下:
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)); }
调用后可正常输出不同验证规则的结果:
The Name field is required. The Name field is required. The Password field is required.
但该自定义验证逻辑无法适配Entity Framework的自动校验逻辑;禁用EF自动验证属于临时绕过方案,不属于合理的生产级解决方案。
更新1:验证器内部会缓存验证特性,这是该问题的根本原因,对应实现可参考.NET源码中的
ValidationAttributeStore类。
内容的提问来源于stack exchange,提问作者ranstar74
相关产品推荐
相关产品推荐

