FluentValidation
1、定义
使用流畅的接口和 lambda 表达式来构建强类型验证规则。
要为特定对象定义一组验证规则,您需要创建一个继承自 AbstractValidator<T>
的类,其中 T
是您希望验证的类的类型。
定义一个Customer
类
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; }
}
其中验证规则本身应该在验证器类的构造函数中定义
public class CustomerValidator : AbstractValidator<Customer>
{
}
要为特定属性指定验证规则,可以调用 RuleFor
方法,并传递一个 lambda 表达式来指示要验证的属性。
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(customer => customer.Surname).NotNull();
}
}
如果要运行验证器,需要实例化验证器对象并调用 Validate
方法,传入要验证的对象。
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult result = validator.Validate(customer);
if(! results.IsValid)
{
foreach(var failure in results.Errors)
{
Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage);
}
}
/*ValidationResult results = validator.Validate(customer);
string allMessages = results.ToString("~"); */
其中:Validate
方法返回 ValidationResult
对象,其包含两个属性
IsValid
- 布尔值,表示验证是否成功。
Errors
- 是ValidationFailure 对象的集合,其中包含有关任何验证失败的详细信息。
我们可以在 ValidationResult
上调用 ToString
将所有错误消息合并到一个字符串中。默认情况下,消息将用换行符分隔,如果想自定义此行为,可以将不同的分隔符传递给 ToString
。
注意:如果没有验证错误, ToString()
将返回空字符串。
2、链接验证器
可以针对同一属性将多个验证器链接在一起
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
}
}
上述代码将确保Surname
不为空且不等于字符串“foo”。
3、抛出异常
如果验证失败,我们可以使用 ValidateAndThrow
方法告诉 FluentValidation 抛出异常,而不是返回 ValidationResult
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
validator.ValidateAndThrow(customer);
这将会抛出一个 ValidationException
,其中包含 Errors 属性中的错误消息。
注意 ValidateAndThrow
是一个扩展方法,因此您必须在文件顶部使用 using
语句导入 FluentValidation
命名空间,才能使用此方法可用的。
ValidateAndThrow
方法是 FluentValidation options API 的有用包装器,相当于执行以下操作:
validator.Validate(customer, options => options.ThrowOnFailures());
扩展:如果您需要将抛出异常与规则集结合起来,或验证单个属性,则可以使用以下语法组合这两个选项:
validator.Validate(customer, options =>
{
options.ThrowOnFailures();
options.IncludeRuleSets("MyRuleSets");
options.IncludeProperties(x => x.Name);
});
4、复杂属性
验证器可以重复用于复杂的属性。例如,假设您有两个类:Customer 和 Address:
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 Country { get; set; }
public string Postcode { get; set; }
}
public class AddressValidator : AbstractValidator<Address>
{
public AddressValidator()
{
RuleFor(address => address.Postcode).NotNull();
//etc
}
}
CustomerValidator
定义中使用到 AddressValidator
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(customer => customer.Name).NotNull();
RuleFor(customer => customer.Address).SetValidator(new AddressValidator());
}
}
当在 CustomerValidator
上调用 Validate
时,它将运行 CustomerValidator
和 AddressValidator
中定义的验证器,并将结果合并到单个 ValidationResult
中。
注意:如果子属性为 null,则子验证器将不会被执行。
您可以内联定义子规则,而不是使用子验证器,例如:
RuleFor(customer => customer.Address.Postcode).NotNull()
在这种情况下,不会自动对 Address
执行 null 检查,因此您应该显式添加一个条件
RuleFor(customer => customer.Address.Postcode).NotNull().When(customer => customer.Address != null)
5、集合
我们可以使用 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
集合中的每个项目运行 NotNull 检查。
当集合是另一个复杂对象时,您还可以将 RuleForEach
与 SetValidator
组合起来
public class Customer
{
public List<Order> Orders { get; set; } = new List<Order>();
}
public class Order
{
public double Total { get; set; }
}
public class OrderValidator : AbstractValidator<Order>
{
public OrderValidator()
{
RuleFor(x => x.Total).GreaterThan(0);
}
}
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleForEach(x => x.Orders).SetValidator(new OrderValidator());
}
}
或者,从 FluentValidation 8.5 开始,您还可以使用 ChildRules
方法为内嵌的子集合元素定义规则
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleForEach(x => x.Orders).ChildRules(order =>
{
order.RuleFor(x => x.Total).GreaterThan(0);
});
}
}
我们也可以选择使用 Where
方法在验证中包含或排除集合中的某些项目。需要注意,这必须直接在调用 RuleForEach
之后出现
RuleForEach(x => x.Orders)
.Where(x => x.Cost != null)
.SetValidator(new OrderValidator());
6、覆盖默认消息
我们可以通过在验证器定义上调用 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}");
7、占位符
如上例所示,消息可以包含特殊值的占位符,例如 {PropertyName}
- 将在运行时替换。每个内置验证器都有自己的占位符列表。
- 所有验证器中使用的占位符有:
{PropertyName}
– 正在验证的属性的名称
{PropertyValue}
– 正在验证的属性值 ,这些包括谓词验证器( Must
验证器)、电子邮件和正则表达式验证器。
- 用于比较验证器
Equal
、 NotEqual
、 GreaterThan
、 GreaterThanOrEqual
等
{ComparisonValue}
– 属性应与之比较的值
{ComparisonProperty}
– 进行比较的属性名称(如果有)
- 用于长度验证器
{MinLength}
– 最小长度
{MaxLength}
– 最大长度
{TotalLength}
– 输入的字符数
8、覆盖属性名称
默认验证错误消息包含正在验证的属性名称。
RuleFor(customer => customer.Surname).NotNull();
那么默认的错误消息将是”Surname’ must not be empty.“虽然您可以通过调用 WithMessage
覆盖整个错误消息,但您也可以通过调用 WithName
仅替换属性名称,
RuleFor(customer => customer.Surname).NotNull().WithName("Last name");
现在错误消息将是”Last name’ must not be empty.“
9、条件
When
和 Unless
方法可用于指定控制规则何时执行的条件。
例如, CustomerDiscount
属性上的此规则仅在 IsPreferredCustomer
为 true
时执行:
RuleFor(customer => customer.CustomerDiscount).GreaterThan(0).When(customer => customer.IsPreferredCustomer);
Unless
方法与 When
完全相反。
如果需要为多个规则指定相同的条件,则可以调用顶级 When
方法,而不是在规则末尾链接 When
调用:
When(customer => customer.IsPreferred, () => {
RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
RuleFor(customer => customer.CreditCardNumber).NotNull();
});
这次,条件将应用于这两个规则。还可以链接对 Otherwise
的调用,这将调用与条件不匹配的规则:
When(customer => customer.IsPreferred, () => {
RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
RuleFor(customer => customer.CreditCardNumber).NotNull();
}).Otherwise(() => {
RuleFor(customer => customer.CustomerDiscount).Equal(0);
});
默认情况下,FluentValidation 会在对 RuleFor
的同一调用中将条件应用于所有前面的验证器。如果您只想将条件应用于紧邻该条件之前的验证器,则必须明确指定:
RuleFor(customer => customer.CustomerDiscount)
.GreaterThan(0).When(customer => customer.IsPreferredCustomer, ApplyConditionTo.CurrentValidator)
.EqualTo(0).When(customer => ! customer.IsPreferredCustomer, ApplyConditionTo.CurrentValidator);
如果未指定第二个参数,则默认为 ApplyConditionTo.AllValidators
,这意味着该条件将应用于同一链中所有前面的验证器。
在下面的示例中,对 When
的第一次调用仅适用于对 Matches
的调用,而不适用于对 NotEmpty
的调用。对 When
的第二次调用仅适用于对 Empty
的调用。
RuleFor(customer => customer.Photo)
.NotEmpty()
.Matches("https://wwww.photos.io/\d+\.png")
.When(customer => customer.IsPreferredCustomer, ApplyConditionTo.CurrentValidator)
.Empty()
.When(customer => ! customer.IsPreferredCustomer, ApplyConditionTo.CurrentValidator);
一次调用仅适用于对 Matches
的调用,而不适用于对 NotEmpty
的调用。对 When
的第二次调用仅适用于对 Empty
的调用。
RuleFor(customer => customer.Photo)
.NotEmpty()
.Matches("https://wwww.photos.io/\d+\.png")
.When(customer => customer.IsPreferredCustomer, ApplyConditionTo.CurrentValidator)
.Empty()
.When(customer => ! customer.IsPreferredCustomer, ApplyConditionTo.CurrentValidator);