You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

验证后无法修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 13:06:08