18

我正在使用 FluentValidation 验证对象内部的集合,将集合项的元素与父对象的元素进行比较。

目标输出是接收集合中每个失败项目的 ValidationFailures,而不仅仅是使集合失败。

我有一个软件订单,其中包含软件项目列表。如果订单是遗留系统,则所选软件只能是遗留软件,反之亦然,非遗留系统只能有非遗留软件。

我的模型:

public class SoftwareOrder
{
   public bool IsLegacySystem;
   public List<SoftwareItem> Software;
   (...other fields...)
}
public class SoftwareItem
{
   public bool Selected;
   public bool IsLegacySoftware;
   public int SoftwareId;
}

验证器:

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder>
{
   public SoftwareOrderValidator()
   {
     (..other rules..)

     When(order => order.IsLegacySystem == true, () =>
     {
        RuleForEach(order => order.SoftwareItem)
           .SetValidator(new SoftwareItemValidator(true));
     });
     When(order => order.IsLegacySystem == false, () =>
     {
        RuleForEach(order => order.SoftwareItem)
           .SetValidator(new SoftwareItemValidator(false));
     });
   }
}
public class SoftwareItemValidator : AbstractValidator<SoftwareItem>
{
   public SoftwareItemValidator(bool IsLegacySystem)
   {
     When(item => item.Selected, () =>
     {
        RuleFor(item => item.IsLegacySoftware)
            .Equal(IsLegacySystem).WithMessage("Software is incompatible with system");
     });
   }
}

正如你所看到的,我通过为每个条件设置一个 When 来实现这一点。它有效,但它违反了 DRY,并且在具有两个以上条件的情况下使用是不实际的。

理想情况下,我希望有一个 RuleForEach 可以做到这一点,不需要何时,例如:

RuleForEach(order => order.SoftwareItem)
   .SetValidator(new SoftwareItemValidator(order => order.IsLegacySystem));

但是我看不到任何将 IsLegacySystem 传递给该构造函数的方法。

4

3 回答 3

28

两年后,在看到这个未回答的问题获得了多少浏览量之后,我决定再试一次。我想出了两个答案。

第一个答案是问题中描述的情况的最佳解决方案。

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder>
{
   public SoftwareOrderValidator()
   {
      RuleForEach(order => order.SoftwareItem)
         .Must(BeCompatibleWithSystem)
         .WithMessage("Software is incompatible with system");

   }

   private bool BeCompatibleWithSystem(SoftwareOrder order, SoftwareItem item)
   {
      if (item.Selected)
         return (order.IsLegacySystem == item.IsLegacySoftware);
      else
         return true;
   }
}

谓词验证器(又名 Must)可以将对象和属性都作为参数。这允许您直接与 IsLegacySystem 或父对象的任何其他属性进行比较。

您可能不应该使用第二个答案。如果您认为需要将参数传递给 AbstractValidator 的构造函数,我会鼓励您重新评估并找到不同的方法。有了这个警告,这是实现它的一种方法。

基本上,使用虚拟 Must() 允许您在构造函数之外的 lambda 之外设置变量。然后您可以使用它将该值获取到第二个验证器的构造函数中。

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder>
{
   private bool _isLegacySystem;

   public SoftwareOrderValidator()
   {
      RuleFor(order => order.IsLegacySystem)
         .Must(SetUpSoftwareItemValidatorConstructorArg);

      RuleForEach(order => order.SoftwareItem)
         .SetValidator(new SoftwareItemValidator(_isLegacySystem));

   }

   private bool SetUpSoftwareItemValidatorConstructorArg(bool isLegacySystem)
   {
      _isLegacySystem = isLegacySystem;
      return true;
   }
}
public class SoftwareItemValidator : AbstractValidator<SoftwareItem>
{
   public SoftwareItemValidator(bool IsLegacySystem)
   {
     When(item => item.Selected, () =>
     {
        RuleFor(item => item.IsLegacySoftware)
            .Equal(IsLegacySystem).WithMessage("Software is incompatible with system");
     });
   }
}
于 2015-07-02T17:55:12.027 回答
17

我知道这是一个老问题,并且已经给出了答案,但是我今天偶然发现了这个问题,发现 FluentValidation 的当前版本(我使用的是 6.2.1.0)对 SetValidator 有一个新的重载,允许你将 Func 作为参数传递。

所以,你可以这样做:

RuleForEach(x => x.CollectionProperty)
    // x below will referente the Parent class
    .SetValidator(x => new CollectionItemValidator(x.ParentProperty);

希望这可以帮助那里的人。

于 2016-09-05T23:03:11.380 回答
1

这种使用自定义方法的方法怎么样:

public class SoftwareOrder
{
    public bool IsLegacySystem;
    public List<SoftwareItem> Software;
}
public class SoftwareItem
{
    public bool Selected;
    public bool IsLegacySoftware;
    public int SoftwareId;
}

public class SoftwareOrderValidator : AbstractValidator<SoftwareOrder>
{
    public SoftwareOrderValidator()
    {
        Custom(order =>
        {
            if (order.Software == null)
                return null;

            return order.Software.Any(item => order.IsLegacySystem != item.IsLegacySoftware)
               ? new ValidationFailure("Software", "Software is incompatible with system")
               : null;
        });

        // Validations other than legacy check
        RuleFor(order => order.Software).SetCollectionValidator(new SoftwareItemValidator());
    }
}

public class SoftwareItemValidator : AbstractValidator<SoftwareItem>
{
    // Validation rules other than legacy check
    public SoftwareItemValidator()
    {
    }
}
于 2013-09-10T22:26:40.577 回答