如何对带有Action Filter参数验证的MVC控制器进行单元测试?
我之前在项目里实现过一模一样的场景,用Action Filter结合FluentValidation自动验证Web API的参数,下面是完整的实现方案:
完整实现步骤
1. 自定义FluentValidation Action Filter
这个Filter会在Action执行前自动找到参数对应的验证器,完成验证逻辑:
using FluentValidation; using Microsoft.AspNetCore.Mvc.Filters; public class FluentValidationActionFilter : IActionFilter { private readonly IServiceProvider _serviceProvider; // 通过构造注入获取服务容器,用于解析验证器 public FluentValidationActionFilter(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void OnActionExecuting(ActionExecutingContext context) { // 遍历控制器动作的所有参数 foreach (var parameter in context.ActionDescriptor.Parameters) { var paramType = parameter.ParameterType; // 构建对应参数类型的IValidator<T>接口类型 var validatorType = typeof(IValidator<>).MakeGenericType(paramType); // 从服务容器中获取验证器实例 var validator = _serviceProvider.GetService(validatorType) as IValidator; if (validator != null && context.ActionArguments.TryGetValue(parameter.Name, out var paramValue)) { // 执行参数验证 var validationResult = validator.Validate(new ValidationContext<object>(paramValue)); if (!validationResult.IsValid) { // 将验证错误映射到ModelState foreach (var error in validationResult.Errors) { context.ModelState.AddModelError(error.PropertyName, error.ErrorMessage); } // 验证失败时直接返回400 Bad Request,终止后续动作执行 context.Result = new Microsoft.AspNetCore.Mvc.BadRequestObjectResult(context.ModelState); return; } } } } public void OnActionExecuted(ActionExecutedContext context) { // 动作执行后无需额外操作,留空即可 } }
2. 注册服务与Filter
在Program.cs中配置FluentValidation和自定义Filter,让框架能自动发现验证器并应用Filter:
var builder = WebApplication.CreateBuilder(args); // 添加控制器服务 builder.Services.AddControllers(); // 注册FluentValidation服务,自动发现指定程序集中的验证器 builder.Services.AddFluentValidationAutoValidation() .AddFluentValidationClientsideAdapters() // 替换成你的验证器所在的程序集(比如包含Program的程序集,或者单独的验证器程序集) .AddValidatorsFromAssemblyContaining<Program>(); // 注册自定义Action Filter为Scoped服务 builder.Services.AddScoped<FluentValidationActionFilter>(); var app = builder.Build(); // 中间件配置 app.UseHttpsRedirection(); app.UseAuthorization(); // 全局为所有控制器动作添加Filter app.MapControllers().AddFilter<FluentValidationActionFilter>(); app.Run();
3. 创建实体类与对应的验证器
定义你的业务实体,以及继承自AbstractValidator<T>的验证器类:
// 示例实体类 public class UserCreateModel { public string Username { get; set; } public string Email { get; set; } public int Age { get; set; } } // 对应的FluentValidation验证器 public class UserCreateModelValidator : AbstractValidator<UserCreateModel> { public UserCreateModelValidator() { RuleFor(x => x.Username) .NotEmpty().WithMessage("用户名不能为空") .MinimumLength(3).WithMessage("用户名长度不能少于3个字符"); RuleFor(x => x.Email) .NotEmpty().WithMessage("邮箱地址不能为空") .EmailAddress().WithMessage("请输入有效的邮箱格式"); RuleFor(x => x.Age) .GreaterThanOrEqualTo(18).WithMessage("年龄必须年满18周岁"); } }
4. 控制器使用示例
在控制器动作中直接使用实体参数即可,Filter会自动完成验证:
[ApiController] [Route("api/users")] public class UsersController : ControllerBase { [HttpPost] public async Task<IActionResult> CreateUser([FromBody] UserCreateModel model) { // 验证不通过的话,Filter已经提前返回BadRequest了,这里直接写业务逻辑 // 比如保存用户到数据库等操作 return Ok(new { Message = "用户创建成功", User = model }); } }
一些实用注意点
- 嵌套对象验证:如果你的实体包含嵌套对象,只要为嵌套对象也定义对应的
AbstractValidator<T>,FluentValidation会自动递归验证嵌套属性。 - 局部应用Filter:如果不想全局应用Filter,可以在特定控制器或动作上添加
[ServiceFilter(typeof(FluentValidationActionFilter))]特性来启用验证。 - 验证器可见性:确保你的验证器类是
public的,否则FluentValidation无法自动发现它们。
内容的提问来源于stack exchange,提问作者Viktor
相关产品推荐
相关产品推荐

