使用 FluentValidation 验证集合,为 属性 返回一个失败的规则错误

Validate a collection with FluentValidation returning one failed-rule error for a property

我刚开始使用 FluentValidation v9.x,想知道如何验证集合中的规则。

基本上如果我有一个物质集合

public class Substance
{
  public int? SubstanceId { get; set; }
  public string SubstanceName { get; set; }
  public decimal? SubstanceAmount { get; set; }
  public int? SubstanceUnitId { get; set; }
  public int? SubstanceRouteId { get; set; }
  public DateTimeOffset? SubstanceTime { get; set; }
}

我想验证一下:

如果规则失败,我希望针对每条规则发回一条错误消息,而不是针对每种物质,这就是现在发生的以下情况:

RuleForEach(x => x.SubstanceList).SetValidator(new SubstanceValidator(RuleSetsToApply));

public class SubstanceValidator : AbstractValidator<Substance>
{
  public SubstanceValidator(List<ValidationRule> RuleSetsToApply)
  {
    string ruleSetName = "SubstanceAmountUnit";
    RuleSet(ruleSetName, () => {
      RuleFor(x => x.SubstanceAmount).NotNull().NotEmpty();
      RuleFor(x => x.SubstanceUnitId).NotNull().NotEmpty().GreaterThan(0);
    });

    ruleSetName = "SubstanceIngestion";
    RuleSet(ruleSetName, () => {
      RuleFor(x => x.SubstanceTime).NotNull().NotEmpty();
    });

    ruleSetName = "SubstanceRoute";
    RuleSet(ruleSetName, () => {
      RuleFor(x => x.SubstanceRouteId).NotNull().NotEmpty().GreaterThan(0);
    });
  }
}

所以如果我有五种物质并且

我怎样才能做到这一点?

如果我没有正确理解问题,在这种情况下,我会在父 SubstanceList 而不是 Substance 实体验证器上定义验证规则。

为简洁起见,我没有考虑您的附加规则集逻辑,因为我对此了解不够,也不知道它是否真的需要。但是,以下会产生您拥有的三个验证案例:

  • if SubstanceAmount and SubstanceUnitId has values, and
  • if SubstanceRouteId has a value, and
  • if SubstanceTime has a value on any of the substance items.
RuleFor(x => x.SubstanceList)
    .Must(x => x != null ? x.All(y => y.SubstanceAmount.HasValue && y.SubstanceUnitId.HasValue && y.SubstanceUnitId.Value <= 0) : true)
    .WithMessage(x => "One or more substance amounts or unit ids has not been provided, and/or one or more unit ids is less than or equal to 0.");

RuleFor(x => x.SubstanceList)
    .Must(x => x != null ? x.All(y => y.SubstanceTime.HasValue) : true)
    .WithMessage(x => "One or more substance times has not been provided.");

RuleFor(x => x.SubstanceList)
    .Must(x => x != null ? x.All(y => y.SubstanceRouteId.HasValue && y.SubstanceRouteId.HasValue && y.SubstanceRouteId.Value <= 0) : true)
    .WithMessage(x => "One or more substance route ids has not been provided or is less than or equal to 0.");

根据您的示例场景:

So if I have five substances and

  • the first substance fails Rule #2,
  • the third fails Rule #1 and #2 and
  • the fourth fails on the Rule #3,

I would expect one error for each rule to be returned, even though Rule #2 has failed twice.

当我设置具有以下条件的物质序列时:

  • 第一个没有实质时间(规则 #2)
  • 第三个没有物质数量(规则#1)或时间(规则#2),并且
  • 第四条物质路线 ID <= 0(规则 #3)。

我得到了我认为是期望的输出:

还有其他方法吗?结果是基本上区分错误消息,所以是的,可能还有其他方法。如果您手动调用 Validate,您会想到的是 post 处理验证结果并确保错误消息是不同的。我更喜欢上面的方法,感觉更确定,也让我有机会提供合适的错误信息。

工作 LINQPad 示例:

void Main()
{
    var fixture = new Fixture();
    var substances = new List<Substance>();
    substances.Add(fixture.Build<Substance>().Without(x => x.SubstanceTime).Create());
    substances.Add(fixture.Create<Substance>());
    substances.Add(fixture.Build<Substance>().Without(x => x.SubstanceAmount).Without(x => x.SubstanceTime).Create());
    substances.Add(fixture.Build<Substance>().With(x => x.SubstanceRouteId, -1).Create());
    substances.Add(fixture.Create<Substance>());
    Console.WriteLine(substances);

    var foo = new Foo() { SubstanceList = substances };
    var validator = new FooValidator();
    var validationResult = validator.Validate(foo);
    Console.WriteLine(validationResult.Errors.Select(x => x.ErrorMessage));
}

public class Substance
{
    public int? SubstanceId { get; set; }
    public string SubstanceName { get; set; }
    public decimal? SubstanceAmount { get; set; }
    public int? SubstanceUnitId { get; set; }
    public int? SubstanceRouteId { get; set; }
    public DateTimeOffset? SubstanceTime { get; set; }
}

public class Foo
{
    public List<Substance> SubstanceList { get; set; }
}

public class FooValidator : AbstractValidator<Foo>
{
    public FooValidator()
    {
        RuleFor(x => x.SubstanceList)
            .Must(x => x != null ? x.All(y => y.SubstanceAmount.HasValue && y.SubstanceUnitId.HasValue && y.SubstanceUnitId.Value <= 0) : true)
            .WithMessage(x => "One or more substance amounts or unit ids has not been provided, and/or one or more unit ids is less than or equal to 0.");

        RuleFor(x => x.SubstanceList)
            .Must(x => x != null ? x.All(y => y.SubstanceTime.HasValue) : true)
            .WithMessage(x => "One or more substance times has not been provided.");

        RuleFor(x => x.SubstanceList)
            .Must(x => x != null ? x.All(y => y.SubstanceRouteId.HasValue && y.SubstanceRouteId.HasValue && y.SubstanceRouteId.Value <= 0) : true)
            .WithMessage(x => "One or more substance route ids has not been provided or is less than or equal to 0.");
    }
}