FluentValidation 规则链接不会在第一次失败时停止

FluentValidation rules chaining not stopping at first failure

我有一个模型:

public class DTO
{
    public int[] StatementItems { get; set; }
}

我想验证一下:

  1. StatementItems 不为空
  2. StatementItems不为空
  3. StatementItems 不包含任何重复 ID

我创建的验证规则链是:

RuleFor(x => x.StatementItems).NotNull().NotEmpty().Must(x => x.Distinct().Count() == x.Count());

我有一个测试:

_validator.ShouldHaveValidationErrorFor(x => x.StatementItems, null as int[]);

当我 运行 测试传递空值时,我预计它会在链的第一条规则 (NotNull()) 上失败并停在那里。但是,它抱怨 Must() 中使用的 lamda 值为空。

如果 NotNull() 失败,我认为 Must() 不应该是 运行 是不是错了?如果可以,这个规则应该怎么写?

谢谢

我在 FluentValidation 文档中没有看到它实际上保证了 short-circuiting。

如果您查看其来源:

public virtual ValidationResult Validate(ValidationContext<T> context)
{
  ...
  var failures = nestedValidators.SelectMany(x => x.Validate(context));
  return new ValidationResult(failures);
}

它将运行 通过*所有* 验证器(带有SelectMany())和returns 错误列表。

您唯一的选择似乎是强制检查您的 Must 规则。

.Must(x => x!= null && x.Distinct().Count() == x.Count())
//or, fluently:
.Must(x => x.Distinct().Count() == x.Count()).When(x => x! = null)

编辑: 我打算建议,由于 Validate() 是虚拟的,您可以在验证器中覆盖它以使其成为 short-circuit。但后来我意识到 nestedValidators 列表是私有的。所以是的,不..

尽管@NPras 的回答确实为我提供了解决方案,但我不喜欢复制 NotNull 规则这一事实。在对 FluentValidation 进行更多研究后,我使用 DependentRules:

实现了它
RuleFor(x => x.StatementItems).NotNull().NotEmpty()
            .DependentRules(d =>
                d.RuleFor(x => x.StatementItems).Must(x => x.Distinct().Count() == x.Count())
            );

所以现在 Must 条件仅在前两个规则有效时触发。

查看 FluentValidation 的 cascade mode。您可以像这样在第一次失败时使其 short-circuit:

this.RuleFor(x => x.StatementItems)
   .Cascade(CascadeMode.Stop)
   .NotNull()
   .NotEmpty()
   .Must(x => x.Distinct().Count() == x.Count());

此外,您可以在 AbstractValidator 子类的构造函数中配置它。那么你就不需要把它放在每条规则上了。

public MyInputValidator()
{
  this.CascadeMode = CascadeMode.Stop;
}