将对象的元素传递给FluentValidation SetValidator的构造函数

fri*_*gle 11 c# lambda fluentvalidation

我正在使用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;
}
Run Code Online (Sandbox Code Playgroud)

验证:

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");
     });
   }
}
Run Code Online (Sandbox Code Playgroud)

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

理想情况下,我希望有一个可以执行此操作的RuleForEach,不需要Whens,例如:

RuleForEach(order => order.SoftwareItem)
   .SetValidator(new SoftwareItemValidator(order => order.IsLegacySystem));
Run Code Online (Sandbox Code Playgroud)

但我看不出有任何方法可以将IsLegacySystem传递给该构造函数.

fri*_*gle 20

两年后,在看到这个悬而未决的问题有多少观点之后,我决定再拍一次.我想出了两个答案.

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

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;
   }
}
Run Code Online (Sandbox Code Playgroud)

Predicate Validators(aka Must)可以将object和property作为参数.这允许您直接与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");
     });
   }
}
Run Code Online (Sandbox Code Playgroud)


Rod*_*ira 8

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

所以,你可以这样做:

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

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