一..NET Core 内置数据校验的不足
无论是通过在属性上标注校验规则 Attribute的方式,还是实现IValidatableObject接
口的方式,我们的校验规则都是和模型类耦合在一起的,这违反了面向对象的“单一职责原则”。
NET Core中内置的校验规则不够多,很多常用的校验需求都需要我们编写意定义,验规则。
二. FluentValidation
上边我们提到了.NET Core 中内置数据校验机制的不足,这里推推荐一个优秀的数据校验框架--FluentValidation,它可以让我们用类似于EF Core 中Fluent API的方式进行校验规则的配置,也就是我们可以把对模型类的校验放到单独的校验类 FluentValidation 可以用于控制台、WPF、ASPNET Core 等各种NET目中。
三.FluentValidation的基本使用
第1步,在项目中安装 NuGet包FluentValidation.AspNetCore.
第2步,在 Program.cs 中添加注册相关服务的代码
1. bullder.Services.addfiuentValidation(fv =>{ |
2.Assembly assembly = Assembly.GetExecutingAssembly();
3. fv.RegiaterValidatorsFromAssenbly(assembly);
4 });
RegisterValidatorsFromAssembly方法用于把指定程序集中所有实现了 IValidator 接口的数据校验类注册到依赖注入容器中,因为这个例子中只有一个项目,所有的数据校验类也都写这个项目中,所以我们用Assembly.GetExecutingAssembly获取入口项目的程序集。在实际的项目中,数据校验类可能会位于多个程序集中,我们可以调用 RegisterValidatorsFromAssemblies米指定这些程序集进行注册。
第3步,编写一个模型类 Login2Request,
Login2Request 类
public record Login2Request(string Email, string Pansword, atring Paasmord2);
可以看到,这里的Login2Request 类只是一个普通的C#类,没有标注任何的 Altribuk成者实现任何的接口,这个类的唯一责任就是传递数据。
第4步,编写一个维承自 AbstractValidator 的数据校验类
Login2RequestValidator类
1 public class Login2RequestValidator: AbatraceValidator<Login2Request>
2{
3public Login2RequestValidator()
4{
5RuleFor(x=>x.Email).NotNull().EmailAnddress ()
6 .Must(v=>v.EndsWith("@qq.com") || v.EndsWith("@163.com"))
7 . WithMessage(“只支持QQ和 163 邮箱");
8 RuleFor(x =>xPaseword),Hot0a11 1) .Lengtn(3, 10)
9 .WithMessage("密码长度必介于3到10 之间”)
10 .Equal(x => x.Passwbrd2).withMessage("两次密码必法一致”)
11 }
12 }
数据校验类一般继承自AbstractValidator,AbstractValidator类是一个泛型类,我们需要通过泛型参数指定这个数据校验类对哪个类进行校验,校验规则写到校验类的构造方法中;我们通过 RuleFor 来指定要对哪个属性进行校验,然后使用NotNull(非空)EmailAddress(邮箱地址)、Length(字符串长度)等内置方法来编写校验规则:多个校验规则可以采用链式调用的写法:每个需要校验的属性对应一组 RuleFor 调用,上面的第 5~7 行代码用于对 Emaii 性进行校验,而第 8~10 行代码用于对 Password 属性进行校验。
FluentVabdation 中内置了丰富的校验规则,如果想编写自定义校验规别,我们可以在 Must方法中编写,如第6行代码所示。
FluentValidation 内置的校验规则有默认的报错信息,我们也可以通过WithMessage方法自定义报错信息,WithMessage方法设置的报错信息只作用于它之前的那个校验规则,如第7行代码和第 10 行代码所示。
第5步,我们编写一个操作方法,用 Login2Request 作为参数。然后我们在客户端向这个操作方法对应的路径发送非法的数据,服务器端响应的报文如图所示。
可以看到,使用 FluentValidation 以后,我们可以把数据校验的规则写到单独的数据校验类中,这样模型类和数据校验类各司其职,符合“单一职责原则”,而且在 FluentVialiciation 中编写自定义校验代码也更加简单。FlucntValidation 和NET Core内置的校验方式是可以共存的也就是我们可以一部分校验规则用 FluentVabdation 写,另一部分校验规则用:NET Core 内置的校验方式写,不过为了代码的统一,建议不要混用这两者。
四. FluentValidation中注入服务
在编写数据校验代码的时候,有时候我们需要调用依赖注入容器中的服务, FlucntValidation中的数据校验类是通过依赖注入容器实例化的,因此我们同样可以通过构造方法来向数据校验类中注入服务。
实例
数据库的一张表中记录着系统中已有的用户名,密码等信息,用户表的实体类为User,我们通过TestDbContex读取数据
下面来实现在登录的时候检查用户名是否存在的校验类。
定文一个类Login]Requcs,这个类包含 UserName(用户名),Passwond(密码)两个属
性。
然后,我们再编写一个对 Login3Request 进行校验的类,
1public class Login3RequestValidator:AbstractValidator<Login3Request >
2{
3publisLogin3RequestValidator(TestDbcontext dbCtx)
4{
5RuleFor(x => x.UserName).NotNull()
6 .Must(name=>dbCtx.User.Any(u=>u.UserName==name))
7 .withMessage(c=>$"用户名{c.UserName}不存在“);
8 }
9 }
可以看到,我们通过构造方法注入了 TestDbContext,并且在第6行代码的Must方法中使用了TestDbContext服务检查用户名是否存在。值得注意的是,我们在第7行代码的 withMessage方法中还可以用 Lambda表达式的形式使用模型类中的属性对报错信息进行格式化。
由于异步代码通常能给系统带来更好的吞吐量,因此我们编写代码的原则是“能用异步妈就不要用同步代码”。第6行代码使用同步的 Any方法判断用户名是否存在,而EFCom有一个AnyAsync方法,它是异步版本的Any方法。在FluentValidation 中我们可以用MustAsync和 AnyAsync 来编写异步校验规则,
异步校验规则代码
5RuleFor(x => x.UserName).NotNull()
6 .MustAsync(name,_ )=>dbCtx.Users.AnyAsync(u=>u.UserName==name))
7 .withMessage(c=>$"用户名{c.UserName}不存在“);