实体验证-FluentValidation
首先明白两个概念
- 自动验证:就是在请求进入到控制器前FluentValidation就自行完成实体的验证并做错误返回,
- 优点:简单 少一些手动调用的代码
- 缺点:灵活性差,不好控制,不支持异步等。官方不建议使用自动验证
- 手动验证:在代码中手动示例验证类,进行调用 并自己判断校验结果 获取错误信息,自行做错误返回控制
- 优点:控制方便,返回结构可控制
- 缺点:需要手动创建并传入校验体,重复代码较多(可自己做一次封装,这样就不多了)
如下是官方原文:
With automatic validation, FluentValidation plugs into the validation pipeline that’s part of ASP.NET Core MVC and allows models to be validated before a controller action is invoked (during model-binding). This approach to validation is more seamless but has several downsides:
Auto validation is not asynchronous: If your validator contains asynchronous rules then your validator will not be able to run. You will receive an exception at runtime if you attempt to use an asynchronous validator with auto-validation.
Auto validation is MVC-only: Auto-validation only works with MVC Controllers and Razor Pages. It does not work with the more modern parts of ASP.NET such as Minimal APIs or Blazor.
Auto validation is hard to debug: The ‘magic’ nature of auto-validation makes it hard to debug/troubleshoot if something goes wrong as so much is done behind the scenes.
We do not generally recommend using auto validation for new projects, but it is still available for legacy implementations.
需要的扩展包
- FluentValidation.AspNetCore :自动验证需要的包,若不需要自动验证只使用手动验证的话 可以不添加这个包
- FluentValidation.DependencyInjectionExtensions :自动注册所有的扩展验证类到服务管道
添加服务注册
下面的代码我做了一个类的封装,直接在Program.cs中可以用builder.Services.AddFluentValidationAutoValidation();
这样来使用
/// <summary>
/// 添加 FluentValidation 到容器
/// 自动注册所有验证器
/// </summary>
public static void AddFluentValidationSetup(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (!Appsettings.app(new string[] { "Startup", "FluentValidation", "Enabled" }).ObjToBool())
return;
if (Appsettings.app(new string[] { "Startup", "FluentValidation", "AutoValidation" }).ObjToBool()) // 通过配置文件来控制是否需要使用自动校验
services.AddFluentValidationAutoValidation(); // 注册这个会 启用自动验证,在控制器前进行实体验证
services.AddValidatorsFromAssemblyContaining(typeof(IFluentValidation));
}
IFluentValidation
这个i接口如下:
/// <summary>
/// 功 能: 一个空接口,验证类继承该接口,用于批量管道注册
/// V0.01 2023-2-16 10:23:22 xliu 初版
/// </summary>
public interface IFluentValidation
{
}
添加两个页面实体
/// <summary>
/// 功 能: 用户信息
/// V0.01 2023-2-13 15:59:21 xliu 初版
/// </summary>
public class UserDto
{
/// <summary>
/// 用户id
/// </summary>
public string UID { get; set; }
/// <summary>
/// 用户类型
/// </summary>
public string UType { get; set; }
/// <summary>
/// 登录名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 角色id
/// </summary>
public string RoleID { get; set; }
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; }
/// <summary>
/// 权限集
/// </summary>
public string Permissions { get; set; }
}
创建两个 验证是否可以直接注册所有的类到服务
public class UserDto2
{
/// <summary>
/// 用户id
/// </summary>
public string UID { get; set; }
/// <summary>
/// 用户类型
/// </summary>
public string UType { get; set; }
/// <summary>
/// 登录名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 角色id
/// </summary>
public string RoleID { get; set; }
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; }
/// <summary>
/// 权限集
/// </summary>
public string Permissions { get; set; }
}
创建实体验证方法
/// <summary>
/// 功 能: user请求实体验证
/// V0.01 2023-2-15 16:56:11 xliu 初版
/// </summary>
public class UserValidation: AbstractValidator<UserDto>, IFluentValidation
{
public UserValidation()
{
RuleFor(p => p.Name).NotEmpty().WithMessage("用户名不能为空");
}
}
/// <summary>
/// 功 能: user请求实体验证
/// V0.01 2023-2-15 16:56:11 xliu 初版
/// </summary>
public class UserValidation2: AbstractValidator<UserDto2>, IFluentValidation
{
public UserValidation2()
{
RuleFor(p => p.RoleID).NotEmpty().WithMessage("角色ID不能为空");
RuleFor(p => p.RoleName).NotEmpty();
}
}
开始验证
自动验证
注意 自动验证需要注入自动验证那个服务
services.AddFluentValidationAutoValidation();
创建两个控制器
_model 是一个返回实体
/// <summary>
/// 测试 IFluentValidation 效果
/// </summary>
/// <param name="userDto2"></param>
/// <returns></returns>
[HttpPost]
[Route("CreateTokenByUser2")]
public async Task<IActionResult> CreateTokenByUser2(UserDto2 userDto2)
{
// 业务代码
return BadRequest(_model);
}
[HttpPost]
[Route("CreateTokenByUser")]
public async Task<IActionResult> CreateTokenByUser(UserDto userDto)
{
// 业务代码
return BadRequest(_model);
}
手动验证
手动验证就需要把那个自动验证的服务注释掉,不然依旧会优先执行自动验证
注入验证器
private IValidator<UserDto> _validator;
public TokenController(IValidator<UserDto> validator)
{
_validator = validator;
}
/// <summary>
/// 根据用户信息创建token
/// </summary>
/// <param name="userDto"></param>
/// <returns></returns>
[HttpPost]
[Route("CreateTokenByUser")]
public async Task<IActionResult> CreateTokenByUser(UserDto userDto)
{
// 这里就是手动验证代码,将需要验证的实体带到验证器
ValidationResult result = await _validator.ValidateAsync(userDto);
if (!result.IsValid)
{
_model.Code = 401;
_model.Msg = result.Errors.ToArray().ToString();
return Ok(_model);
}
return Ok(_model);
}
结果
UserDTO 自动验证
传入一个空的实体
传入正确的实体
加了创建token的业务
UserDTO2自动验证
传入一个空的实体
可以看出利用 IFluentValidation 这个接口做批量注册是成功的
然后再看看手动验证的方式
UserDTO 手动验证
可以看到,格式是按照我们的返回实体来做的 使得结构可以控制。但是我没有正确的获取 实体错误信息,使得msg有问题。
验证错误信息在 result.Errors 中。
使用可以参照其他的文章会比较好,这里只是简单的做一个介绍和记录。
记录
简单的记录下遇到的坑,看到FluentValidation这个验证 感觉挺好的,想用。但是看到好多文章都是介绍使用 没有一篇在说怎么做批量注入的,都只是按照官网文档做了一个类的注入。想着不合理 应该是批量注入的方法,找了好久 终于在外网看到了一个解决方法:网址
不过很可惜,这是21年时的方案,现在官方已经废弃这种方式了
变更记录
可是最新官网介绍,也只有注入一个验证器的示例:
通过反编译,可以看到是也是通过类似反射的方式拿到哪些使用过这type的类 全部注册到服务。
之后就想着,弄一个空接口 让所有的验证器继承并实现该接口,那就可以去找所有实现过该接口的类 那这些就是我需要注册进去的验证器了。
开始打算直接将整个model层的都通过autofac全部注入,想想太大了 太不合理,后又打算通过反射去找符合条件的类 再注册,感觉不好看就没继续。