ValidationAttribute注入服务实现非侵入式客户端验证方案问询
解决自定义ValidationAttribute客户端验证的服务依赖问题
你遇到的这个问题很典型——ASP.NET的ValidationAttribute特性本身是编译时构造的,没法直接依赖注入服务,而客户端验证需要把服务器端的无效ID集合传递到前端,所以关键是在AttributeAdapter渲染客户端验证属性的时机(服务器渲染页面时)获取服务并拿到数据。
下面给你两种可行的思路,优先推荐第一种通用方案:
方案一:在AttributeAdapter中通过HttpContext获取服务
在AddValidation方法里,你可以从ClientModelValidationContext的ViewContext.HttpContext.RequestServices拿到DI容器中的服务,这样就能获取到IProductService并拿到无效ID集合,再通过MergeAttribute把数据添加到客户端的HTML属性中。
修改你的AttributeAdapter代码:
public class InvalidIdAttributeAdapter : AttributeAdapterBase<InvalidIdAttribute> { private readonly InvalidIdAttribute _attribute; public InvalidIdAttributeAdapter(InvalidIdAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer) { _attribute = attribute; } public override void AddValidation(ClientModelValidationContext context) { // 从HttpContext的RequestServices获取服务 var productService = context.ViewContext.HttpContext.RequestServices.GetService<IProductService>(); if (productService == null) { throw new InvalidOperationException("请确保IProductService已注册到DI容器中"); } // 获取无效ID集合并格式化为前端可读取的JSON字符串 var invalidIds = productService.GetIds() .Where(p => p.Invalid) .Select(p => p.Id); var invalidIdsJson = System.Text.Json.JsonSerializer.Serialize(invalidIds); // 添加客户端验证所需的data-val属性 MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-invalidid", GetErrorMessage(context)); MergeAttribute(context.Attributes, "data-val-invalidid-invalidids", invalidIdsJson); } public override string GetErrorMessage(ModelValidationContextBase validationContext) { return _attribute.FormatErrorMessage(validationContext.ModelMetadata.GetDisplayName()); } }
注册适配器提供器
要让ASP.NET识别你的适配器,需要注册IValidationAttributeAdapterProvider,在Program.cs(或Startup.cs)中添加:
builder.Services.AddSingleton<IValidationAttributeAdapterProvider, InvalidIdAttributeAdapterProvider>(); // 适配器提供器实现 public class InvalidIdAttributeAdapterProvider : IValidationAttributeAdapterProvider { private readonly IValidationAttributeAdapterProvider _innerProvider; public InvalidIdAttributeAdapterProvider(IValidationAttributeAdapterProvider innerProvider) { _innerProvider = innerProvider; } public IAttributeAdapter GetAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer) { if (attribute is InvalidIdAttribute invalidIdAttr) { return new InvalidIdAttributeAdapter(invalidIdAttr, stringLocalizer); } return _innerProvider.GetAdapter(attribute, stringLocalizer); } }
编写客户端验证逻辑
最后需要添加jQuery Validate的自定义验证方法,读取我们传递的无效ID集合:
// 注册自定义验证规则 $.validator.addMethod("invalidid", function (value, element, params) { if (!value) return true; // 允许空值,根据你的业务需求调整 const invalidIds = JSON.parse(params); return !invalidIds.includes(parseInt(value, 10)); }); // 绑定不显眼验证的适配器 $.validator.unobtrusive.adapters.add("invalidid", ["invalidids"], function (options) { options.rules["invalidid"] = options.params.invalidids; options.messages["invalidid"] = options.message; });
方案二:提前在控制器/页面中传递数据(备选)
如果不想在Adapter中直接获取服务,你可以在使用该验证特性的控制器或PageModel中,提前把无效ID集合放到ViewData或ModelMetadata.AdditionalValues中,然后在Adapter里读取:
控制器中设置数据:
public IActionResult Edit(int id) { var productService = HttpContext.RequestServices.GetService<IProductService>(); var invalidIds = productService.GetIds().Where(p => p.Invalid).Select(p => p.Id); ViewData["InvalidProductIds"] = System.Text.Json.JsonSerializer.Serialize(invalidIds); return View(); }
Adapter中读取数据:
public override void AddValidation(ClientModelValidationContext context) { var invalidIdsJson = context.ViewContext.ViewData["InvalidProductIds"] as string; if (string.IsNullOrEmpty(invalidIdsJson)) { // 处理无数据的情况,比如跳过客户端验证 return; } // 后续添加data-val属性逻辑和方案一一致 }
这种方式的缺点是需要在每个使用该特性的页面手动设置数据,通用性不如方案一。
注意事项
- 服务注册:确保
IProductService已经正确注册到DI容器(比如builder.Services.AddScoped<IProductService, ProductService>())。 - 数据缓存:如果
GetIds()的数据不经常更新,建议在IProductService中缓存结果,避免每次渲染页面都查询数据库/数据源,提升性能。 - 数据格式:用JSON传递ID集合比用分隔符更可靠,能避免ID包含特殊字符导致的解析错误。
内容的提问来源于stack exchange,提问作者nat
相关产品推荐
相关产品推荐

