ASP.NET Core运行时修改验证定义:自定义ClientValidatorCache实现问询
针对你遇到的ASP.NET Core运行时动态修改验证规则(比如动态标记属性为必填)、且客户端ClientValidatorCache缓存导致变更不生效的问题,我整理了几个更简洁的解决方案,帮你替代当前繁琐的实现思路:
问题背景回顾
你已经通过绕过DefaultModelMetadataProvider的缓存机制,实现了服务端验证规则的动态修改(比如调整ValidationMetadata.IsRequired),但客户端非侵入式验证的data-*属性被ClientValidatorCache缓存,而该类没有可扩展的虚方法或接口,导致动态变更无法同步到前端。当前你采用的重写DefaultValidationHtmlAttributeProvider并自定义CustomClientValidatorCache的方式可行,但实现成本较高。
更优解决方案思路
方案1:直接替换默认的ClientValidatorCache(推荐)
虽然ClientValidatorCache本身没有定义接口,但ASP.NET Core的依赖注入系统允许我们直接替换默认的服务实例。我们可以创建一个不缓存(或按需缓存)的自定义实现,让每次生成客户端验证属性时都重新获取最新的验证器:
- 自定义
DynamicClientValidatorCache:
public class DynamicClientValidatorCache : ClientValidatorCache { // 重写GetValidators方法,移除缓存逻辑,每次都重新生成验证器 public override IEnumerable<IClientModelValidator> GetValidators(ClientValidatorCacheKey key, IClientModelValidatorProvider validatorProvider) { var validators = new List<IClientModelValidator>(); validatorProvider.CreateValidators(new ClientValidatorProviderContext(key.ModelMetadata, validators)); return validators; } }
- 在Program.cs/Startup.cs中替换默认服务:
builder.Services.Replace(ServiceDescriptor.Singleton<ClientValidatorCache, DynamicClientValidatorCache>());
这个方案只需要两步,就能让客户端验证每次都获取最新的规则,完美匹配你服务端的动态修改逻辑,比重写DefaultValidationHtmlAttributeProvider简洁得多。
方案2:通过ModelMetadata附加值传递动态验证状态
如果不想修改缓存机制,可以通过ModelMetadata的AdditionalValues存储动态验证标记,再自定义客户端验证器读取这个值并生成对应的data-*属性:
- 实现自定义客户端验证器:
public class DynamicRequiredClientValidator : IClientModelValidator { public void AddValidation(ClientModelValidationContext context) { if (context.ModelMetadata.AdditionalValues.TryGetValue("IsDynamicallyRequired", out var value) && value is bool isRequired && isRequired) { context.Attributes["data-val"] = "true"; context.Attributes["data-val-required"] = "This field is required."; } } public bool IsReusable => false; }
- 注册验证器提供者:
builder.Services.AddSingleton<IClientModelValidatorProvider, DynamicRequiredClientValidatorProvider>(); public class DynamicRequiredClientValidatorProvider : IClientModelValidatorProvider { public void CreateValidators(ClientValidatorProviderContext context) { context.Validators.Add(new DynamicRequiredClientValidator()); } }
- 在你的自定义
ModelMetadataProvider中设置动态标记:
public class DynamicModelMetadataProvider : DefaultModelMetadataProvider { protected override void CreateValidationMetadata(ValidationMetadataProviderContext context) { base.CreateValidationMetadata(context); // 这里替换成你的运行时动态判断逻辑 if (context.Key.Name == "YourPropertyName" && /* 动态条件 */) { context.ValidationMetadata.AdditionalValues["IsDynamicallyRequired"] = true; } } // 保持你之前绕过缓存的实现逻辑 }
这种方式通过附加元数据传递状态,避开了ClientValidatorCache的缓存限制,适合需要复杂动态规则的场景。
方案3:局部视图/请求级别的动态验证(适合特定场景)
如果只是在特定视图或请求中需要动态修改验证规则,可以通过ViewContext.Items传递状态,再自定义ValidationHtmlAttributeProvider读取并修改属性:
- 继承
DefaultValidationHtmlAttributeProvider:
public class DynamicValidationHtmlAttributeProvider : DefaultValidationHtmlAttributeProvider { public DynamicValidationHtmlAttributeProvider(IModelMetadataProvider metadataProvider, IClientModelValidatorProvider validatorProvider, IOptions<MvcViewOptions> options) : base(metadataProvider, validatorProvider, options) { } public override void AddValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, IDictionary<string, string> attributes) { base.AddValidationAttributes(viewContext, modelExplorer, attributes); // 从ViewContext.Items读取动态验证标记 var cacheKey = $"DynamicRequired_{modelExplorer.ModelMetadata.PropertyName}"; if (viewContext.Items.TryGetValue(cacheKey, out var value) && value is bool isRequired && isRequired) { attributes["data-val"] = "true"; attributes["data-val-required"] = "This field is required."; } } }
- 替换默认服务:
builder.Services.Replace(ServiceDescriptor.Singleton<IValidationHtmlAttributeProvider, DynamicValidationHtmlAttributeProvider>());
- 在Action中设置动态状态:
public IActionResult YourAction() { // 动态标记Name属性为必填 ViewContext.Items["DynamicRequired_Name"] = true; return View(); }
这个方案成本最低,适合局部的动态验证需求,不需要全局修改缓存逻辑。
总结
- 全局动态验证需求:优先选择方案1,替换
ClientValidatorCache实现,代码最简洁,直接匹配服务端的动态修改逻辑。 - 复杂动态规则场景:选择方案2,通过附加元数据传递状态,避开缓存限制。
- 局部视图/请求需求:选择方案3,成本最低,针对性强。
内容的提问来源于stack exchange,提问作者Cyril Iselin

