FluentValidation

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 时,它将运行 CustomerValidatorAddressValidator 中定义的验证器,并将结果合并到单个 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 检查。

当集合是另一个复杂对象时,您还可以将 RuleForEachSetValidator 组合起来

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 验证器)、电子邮件和正则表达式验证器。

  • 用于比较验证器

EqualNotEqualGreaterThanGreaterThanOrEqual

{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、条件

WhenUnless 方法可用于指定控制规则何时执行的条件。

例如, CustomerDiscount 属性上的此规则仅在 IsPreferredCustomertrue 时执行:

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值