由于本文是翻译, 所以将原文原原本本的搬上来, 大家看原文有什么不懂的也可以对照这里.
给出地址: https://fluentvalidation.net/
FluentValidation
fluentvalidation 是一款用于模型验证的组件, 尤其是在 asp.net core 中, 我们通常使用 attribute 的验证方式, 比如 [Required(ErrorMessage="xxxxx")] 这种, 还有很多. 这个组件的好处是可以对模型的验证规则进行集中的管理, 不会让模型的验证散落到程序的各个角落, 所以是一个好东西, 下面来介绍他的使用方法.
安装
最简单的安装方式是通过 nuget 包管理器或者通过 dotnet cli 命令.
- nuget:
- Install-Package FluentValidation
- .net core cli:
- dotnet add package FluentValidation
当我们在 asp.net core 中使用的时候, 还要安装另外一个包:
Install-Package FluentValidation.AspNetCore
创建第一个验证器(validator)
要创建一组针对某个模型的验证器, 首先你要写一个继承了 AbstractValidator<T > 的类. T 就代表一个需要被验证的模型类.
打个比方, 首先假定你有如下一个类:
- public class Customer {
- public int Id { get; set; }
- public string Surname { get; set; }
- public string Forename { get; set; }
- public decimal Discount { get; set; }
- public string Address { get; set; }
- }
你需要通过继承 AbstractValidator<Customer > 来定义一个验证 Customer 的类:
- using FluentValidation;
- public class CustomerValidator : AbstractValidator<Customer> {
- }
验证类中的这些验证规则应该定义在验证类的 Rule 方法中, 该方法必须被重写 .
要为一个类指定一组验证规则, 需要调用 RuleFor 方法, 为该方法传递一个 lambda 表达式, 这个表达式指定了你想要验证的这个类中的某一个属性. 打个比方, 如果你不想让 CUstomer 类中的 Surname 为空, 那么你的验证类看起来像这样:
- using FluentValidation;
- public class CustomerValidator : AbstractValidator<Customer> {
- public CustomerValidator() {
- RuleFor(customer => customer.Surname).NotNull();
- }
- }
重要的说明: 像 NotNull()这样的方法就是一个验证器, validator
要运行这个验证类, 你需要实例化它并且调用 validate 方法, 并将被验证的类对象传入:
- Customer customer = new Customer();
- CustomerValidator validator = new CustomerValidator();
- ValidationResult result = validator.Validate(customer);
Validate 方法返回一个 ValidationResult 类型的对象, 这个对象包含了两个属性:
IsValid: 一个布尔值, 指明这个验证有没有通过.
Errors: 是一个包含了 ValidationFailure 类型对象的一个集合, 这个集合中包含了所有验证失败的细节.
下面的代码将每个错误在 Console 中写出来:
- Customer customer = new Customer();
- CustomerValidator validator = new CustomerValidator();
- ValidationResult results = validator.Validate(customer);
- if(! results.IsValid) {
- foreach(var failure in results.Errors) {
- Console.WriteLine("Property" + failure.PropertyName + "failed validation. Error was:" + failure.ErrorMessage);
- }
- }
ValidationResult 的 ToString 方法会将所有的错误结果都表示出来, 默认情况下, 没个错误都会另起一行, 当然你也可以通过给 ToString 方法传入一些字符来自定义一个分隔符:
- ValidationResult results = validator.Validate(customer);
- string allMessages = results.ToString("~"); // In this case, each message will be separated with a `~`
注意: 如果没有错误发生(验证通过), 那么 ToString 方法会返回一个空字符串.
支持链式调用
你可以在同一个被验证的属性上进行链式的调用:
- using FluentValidation;
- public class CustomerValidator : AbstractValidator<Customer> {
- public CustomerValidator()
- RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
- }
- }
可抛出异常
除了调用 Validate, 你还可以调用
ValidateAndThrow, 这会在验证失败后抛出异常.
- Customer customer = new Customer();
- CustomerValidator validator = new CustomerValidator();
- validator.ValidateAndThrow(customer);
这个方法会抛出一个 ValidationException 异常, 包含了 Erros 属性上的所有错误信息. ValidationException 是一个扩展方法, 请确保你引用了 FluentValidation 这个命名空间.
验证集合类型的属性
你可以通过 RuleForEach 这个方法来对一个集合属性上的所有成员实施一个相同的检测:
- public class Person {
- public List<string> AddressLines {get;set;} = new List<string>();
- }
- public class PersonValidator : AbstractValidator<Person> {
- public PersonValidator() {
- RuleForEach(x => x.AddressLines).NotNull();
- }
- }
上面这个代码会对 AddressLines 这个属性上的每一项都实施一个非空的检测.
不过你可以通过 Where 方法对集合中的某几项元素的检测进行跳过, 主要 Where 方法必须放在 RuleForeach 方法后:
- RuleForEach(x => x.Orders)
- .Where(x => x.Cost != null)
- .SetValidator(new OrderValidator());
上面这段代码在我这里没有通过, 没有 Where 这么一个方法, 但是完全可以调用 Orders 的 Where 扩展方法来挑选元素, 这个没什么我觉得. 由于本文是翻译, 所以将原文原原本本的搬上来, 大家看原文有什么不懂的也可以对照这里.
复杂类型的属性
对于复杂类型的属性, 定义的规则可以复用. 打个比方你有两个类:
- public class Customer {
- public string Name { get; set; }
- public Address Address { get; set; }
- }
- public class Address {
- public string Line1 { get; set; }
- public string Line2 { get; set; }
- public string Town { get; set; }
- public string County { get; set; }
- public string Postcode { get; set; }
- }
然后你定义了一个 AddressValidator:
- public class AddressValidator : AbstractValidator<Address> {
- public AddressValidator() {
- RuleFor(address => address.Postcode).NotNull();
- //etc
- }
- }
如果你要对 Customer 类进行验证, 那么你可以在 CustomerVaidator 上面复用 AddressValidator 这个验证规则:
- public class CustomerValidator : AbstractValidator<Customer> {
- public CustomerValidator() {
- RuleFor(customer => customer.Name).NotNull();
- RuleFor(customer => customer.Address).SetValidator(new AddressValidator());
- }
- }
通过调用 SetValidator 这个方法来解决的.
所以如果你通过调用 CustomerValidator 对象的 Validate 方法时, 会在两个验证类中执行验证的代码.
规则集允许你将验证规则分组在一起, 这些规则可以作为一个组一起执行, 而忽略其他规则:
打个比方, 想像下在 Person 对象上的三个属性(Id,Surname,
Forename), 我们可以将 Surname and Forename 这两个的验证规则放在一个叫做 Names 的组中:
- public class PersonValidator : AbstractValidator<Person> {
- public PersonValidator() {
- RuleSet("Names", () => {
- RuleFor(x => x.Surname).NotNull();
- RuleFor(x => x.Forename).NotNull();
- });
- RuleFor(x => x.Id).NotEqual(0);
- }
- }
现在, 针对 Surname 和 Forename 这两个属性的验证规则被放在一个叫做 "Names" 的验证集合中, 当我们调用 Validate 这个扩展方法 (重载后的) 时, 我们可以为这个扩展方法的 ruleSet 命名参数传递 RuleSet 的名字, 然后我们验证的时候只验证这个 RuleSet:
- var validator = new PersonValidator();
- var person = new Person();
- var result = validator.Validate(person, ruleSet: "Names");// 一个重载的 Validate 方法.
这允许你把一个复杂的验证定义划分为许多细小的隔离的验证规则, 如果你调用 Validate 方法时没有传递一个 ruleSet, 那么验证只会执行那些不在 RuleSet 中的规则. 你可以通过逗号分隔来指定多个 ruleset:
validator.Validate(person, ruleSet: "Names,MyRuleSet,SomeOtherRuleSet")
你也可以通过传递一个 "default" 字面量来指定那些没有在任何 ruleset 的规则:
validator.Validate(person, ruleSet: "default,MyRuleSet")
上面的方法会执行 MyRuleSet 中的规则和那些没有在任何 RuleSet 中的规则.
你也可以通过 "*" 这个字面量来指定所有的规则:
validator.Validate(person, ruleSet: "*")
引入 / 包含规则(INCLUDE)
你可以包含来自其他验证器的规则, 前提是它们验证相同的类型, 这意味着你可以将同一个类型中的验证规则划分成几个 validator:
- public class PersonAgeValidator : AbstractValidator<Person> {
- public PersonAgeValidator() {
- RuleFor(x => x.DateOfBirth).Must(BeOver18);
- }
- protected bool BeOver18(DateTime date) {
- //...
- }
- }
- public class PersonNameValidator : AbstractValidator<Person> {
- public PersonNameValidator() {
- RuleFor(x => x.Surname).NotNull().Length(0, 255);
- RuleFor(x => x.Forename).NotNull().Length(0, 255);
- }
- }
因为上面这两个验证规则的目标都是 Person, 所以你可以在一个验证规则中包含他们:
- public class PersonValidator : AbstractValidator<Person> {
- public PersonValidator() {
- Include(new PersonAgeValidator());
- Include(new PersonNameValidator());
- }
- }
你只能包含相同目标的验证规则.
配置
重写消息
你可以通过 WithMessage 方法来重写验证错误的消息:
RuleFor(customer => customer.Surname).NotNull().WithMessage("Please ensure that you have entered your Surname");
在这个过程中你可以使用占位符来标志一些重要的信息, 比如使用 "{PropertyName}" 可以显式你当前验证的哪个属性的名字:
RuleFor(customer => customer.Surname).NotNull().WithMessage("Please ensure you have entered your {PropertyName}");
在这个例子中, Surname 会替代 {PropertyName} 这个占位符.
配置错误消息参数(占位符)
就像上面的例子中看到的一样, 你可以在 WithMessage 方法中配置一些特殊的占位符, 这些占位符会被一些特殊的值取代. 每一个内置的验证规则 (validator) 都有一些占位符列表:
针对所有的验证规则:
'{PropertyName}': 被验证的属性的名字
'{PropertyValue}': 被验证的属性的值, 包括那些断言验证 (predicate validator) 比如 Must,email 和 regex 验证.
针对比较验证规则(Equal, NotEqual, GreaterThan, GreaterThanOrEqual, etc.):
{ComparisonValue}: 被用来和属性比较的值.
针对长度验证规则:
{MinLength} = 最小长度
{MaxLength} = 最大长度
{TotalLength} = 输入的字符数量
要查看完整的占位符列表请查看这个列表 https://fluentvalidation.net/built-in-validators . 每一个内置的规则器都有它自己的占位符列表.
实际上通过 WithMessage 的另一个重载的方法, 向这个方法传入一个 lambda 表达式就可以自定义你自己的验证消息.
- //Using static values in a custom message:
- RuleFor(customer => x.Surname).NotNull().WithMessage(customer => string.Format("This message references some constant values: {0} {1}", "hello", 5));
- //Result would be "This message references some constant values: hello 5"
- //Referencing other property values:
- RuleFor(customer => customer.Surname)
- .NotNull()
- .WithMessage(customer => $"This message references some other properties: Forename: {customer.Forename} Discount: {customer.Discount}");
- //Result would be: "This message references some other properties: Forename: Jeremy Discount: 100"
重写属性的名称
默认的验证规则中包含了属性的默认名称:
RuleFor(customer => customer.Surname).NotNull();
你可以重写属性名称:
RuleFor(customer => customer.Surname).NotNull().WithName("Last name");
这会导致 {PropertyName} 的值有所改变.
需要注意的是这个改变只会在错误消息中展示, 在 ValidationResult 中, 这个值仍然是属性原来的名称 Surname 而不是 Last name. 如果你想要完全改变属性名, 你可以使用 OverridePropertyName 方法. 这个方法将 ValidationResult 中 Erros 中的属性名也改了(没验证).
这也是 WithName 的一个重载方法, 大部分时候你使用这个方法意味着你在使用 WithName.
条件
When 和 Unless 验证器可以指定规则验证的条件. 比如, CustomerDiscount 属性上的验证只有在
IsPreferredCustomer 为 true 时执行.
RuleFor(customer => customer.CustomerDiscount).GreaterThan(0).When(customer => customer.IsPreferredCustomer);
Unless 正好相反.
如果你想在多个验证上面应用相同的条件, 那你也可以使用 When, 不过用法是这样的:
- When(customer => customer.IsPreferred, () => {
- RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
- RuleFor(customer => customer.CreditCardNumber).NotNull();
- });
When 这个时候是在顶层执行而不是链式的调用了.
设置链式调用的规则
当你链式的对一个属性应用多个规则时, 你可以追加一个链式调用的模式 / 规则, 这个规则用来说明在链式调用中的其中一个验证失败时应该如何处理. 如下, 你有两个验证器:
RuleFor(x => x.Surname).NotNull().NotEqual("foo");
上面这个表示有两个验证器作用于 Surname 属性上, 第一个验证器验证是否为空, 第二个验证是否等于 foo. 当第一个验证失败时, 第二个仍然会执行, 尽管注定也会失败. 可以使用 Cascade 来改变这一默认行为:
RuleFor(x => x.Surname).Cascade(CascadeMode.StopOnFirstFailure).NotNull().NotEqual("foo");
现在, 当第一个验证器失败时, 第二个就不会执行了. 这对于后一个验证器依赖于前一个验证器成功的情况下是非常有用的.
CascadeMode 有两种模式:
Continue(默认): 总会全部执行.
StopOnFirstFailure:
当第一个验证器失败时, 第二个就不会执行了.
可以在验证规则中设置这个:
- public class PersonValidator : AbstractValidator<Person> {
- public PersonValidator() {
- // First set the cascade mode
- CascadeMode = CascadeMode.StopOnFirstFailure;
- RuleFor(...)
- RuleFor(...)
- }
- }
依赖的规则
默认情况下 FluentValidation 里面的所有规则都是互不影响的. 这对于异步验证来说是有效的, 并且针对异步验证来说这也是有意而为之. 然而, 在一些情况下, 你需要确定在一些验证完成的情况下另一些验证才能开始执行, 这通过 DependentRules 来完成.
将 DependentRules 添加在一个规则后面, DependentRules 指定的这个规则依赖于前者. 只有当前者执行完成后它才会执行:
- RuleFor(x => x.Surname).NotNull().DependentRules(() => {
- RuleFor(x => x.Forename).NotNull();
- });
现在只有当 Surname 的验证通过了才会执行 Forname 的验证.
通常情况下这个验证器不推荐使用, 可以用 When 来替代它.
回调
验证失败后你可以用 OnFailure 或者 OnAnyFailure 来进行回调.
- RuleFor(x => x.Surname).NotNull().Must(surname => surname != null && surname.Length <= 200)
- .OnAnyFailure(x => {
- Console.WriteLine("At least one validator in this rule failed");
- })
- RuleFor(x => x.Surname).NotNull().OnFailure(x => Console.WriteLine("Nonull failed"))
- .Must(surname => surname != null && surname.Length <= 200)
- .OnAnyFailure(x => Console.WriteLine("Must failed"));
来源: http://www.bubuko.com/infodetail-2690712.html