.NET 7中如何向自定义ValidationFilter传递FluentValidation的RuleSets
问题背景
自.NET 6发布起,我便开始使用Minimal API,此前采用手动方式实现验证,代码如下:
app.MapPost("api/user", async ([FromService] IValidator<UserDto> validator, [FromBody] UserDto user) => { var validationResult = await validator.ValidateAsync(user); if (!validationResult.IsValid) { return Results.BadRequest(string.Join("/n", validationResult.Errors)); } ... })
.NET 7新增Filters特性后,我实现了自定义验证过滤器,代码如下:
public class ValidationFilter<T> : IEndpointFilter where T : class { private readonly IValidator<T> _validator; public ValidationFilter(IValidator<T> validator) { _validator = validator; } public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var obj = context.Arguments.FirstOrDefault(x => x?.GetType() == typeof(T)) as T; if (obj is null) { return Results.BadRequest(); } var validationResult = await _validator.ValidateAsync(obj); if (!validationResult.IsValid) { return Results.BadRequest(string.Join("/n", validationResult.Errors)); } return await next(context); } }
我通过调用AddEndPointFilter<T>()来使用该过滤器,示例如下:
app.MapPost("api/user", (..) => { ... }).AddEndPointFilter<ValidationFilter>();
该实现运行正常,但我的FluentValidation中定义了RuleSet(),需在PUT请求中使用,请问如何将这些RuleSets传递给自定义的ValidationFilter?
解决方案
要在自定义过滤器中支持FluentValidation的RuleSet,可通过以下几种方式实现:
1. 修改过滤器类,构造注入RuleSet参数
调整ValidationFilter<T>,让它接受RuleSet数组作为构造参数,在验证时传入指定规则集:
public class ValidationFilter<T> : IEndpointFilter where T : class { private readonly IValidator<T> _validator; private readonly string[] _ruleSets; public ValidationFilter(IValidator<T> validator, string[] ruleSets) { _validator = validator; _ruleSets = ruleSets; } public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var obj = context.Arguments.FirstOrDefault(x => x?.GetType() == typeof(T)) as T; if (obj is null) { return Results.BadRequest(); } // 传入指定RuleSet执行验证 var validationResult = await _validator.ValidateAsync(obj, options => options.IncludeRuleSets(_ruleSets)); if (!validationResult.IsValid) { return Results.BadRequest(string.Join("/n", validationResult.Errors.Select(e => e.ErrorMessage))); } return await next(context); } }
使用时通过AddEndpointFilterFactory传递RuleSet:
app.MapPut("api/user/{id}", (UserDto user) => { ... }) .AddEndpointFilterFactory((context, next) => { var validator = context.ServiceProvider.GetRequiredService<IValidator<UserDto>>(); // 指定要使用的RuleSet,比如"Update" var filter = new ValidationFilter<UserDto>(validator, new[] { "Update" }); return filter.InvokeAsync; });
2. 封装扩展方法简化调用
为避免重复编写工厂代码,封装一个扩展方法:
public static class EndpointFilterExtensions { public static RouteHandlerBuilder AddValidationFilter<T>(this RouteHandlerBuilder builder, params string[] ruleSets) where T : class { return builder.AddEndpointFilterFactory((context, next) => { var validator = context.ServiceProvider.GetRequiredService<IValidator<T>>(); var filter = new ValidationFilter<T>(validator, ruleSets); return filter.InvokeAsync; }); } }
使用时更简洁:
// POST请求不指定RuleSet,使用默认规则 app.MapPost("api/user", (UserDto user) => { ... }) .AddValidationFilter<UserDto>(); // PUT请求指定"Update" RuleSet app.MapPut("api/user/{id}", (UserDto user) => { ... }) .AddValidationFilter<UserDto>("Update");
3. 通过端点元数据动态获取RuleSet
如果希望用特性标记指定RuleSet,先定义自定义特性:
[AttributeUsage(AttributeTargets.Method)] public class ValidationRuleSetAttribute : Attribute { public string[] RuleSets { get; } public ValidationRuleSetAttribute(params string[] ruleSets) { RuleSets = ruleSets; } }
修改过滤器,从端点元数据中读取特性信息:
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var obj = context.Arguments.FirstOrDefault(x => x?.GetType() == typeof(T)) as T; if (obj is null) { return Results.BadRequest(); } // 从端点元数据中获取RuleSet配置 var ruleSetAttr = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<ValidationRuleSetAttribute>(); var ruleSets = ruleSetAttr?.RuleSets ?? Array.Empty<string>(); var validationResult = await _validator.ValidateAsync(obj, options => { if (ruleSets.Any()) { options.IncludeRuleSets(ruleSets); } }); if (!validationResult.IsValid) { return Results.BadRequest(string.Join("/n", validationResult.Errors.Select(e => e.ErrorMessage))); } return await next(context); }
使用时给端点标记特性即可:
app.MapPut("api/user/{id}", (UserDto user) => { ... }) .AddEndpointFilter<ValidationFilter<UserDto>>() .WithMetadata(new ValidationRuleSetAttribute("Update"));
内容的提问来源于stack exchange,提问作者Izzy
相关产品推荐
相关产品推荐

