我正在使用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,不需要Whens,如:
RuleForEach(order => order.SoftwareItem)
.SetValidator(new SoftwareItemValidator(order => order.IsLegacySystem));
但我看不出有任何方法可以将IsLegacySystem传递给该构造函数。
答案 0 :(得分:19)
在看到这个未提出问题的答案有多少之后,我决定在2年之后再拍一次。我想出了两个答案。
第一个答案是针对问题中描述的情况的最佳解决方案。
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;
}
}
Predicate Validators(a.k.a Must)可以同时使用object&amp;属性作为参数。这允许您直接与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");
});
}
}
答案 1 :(得分:7)
我知道这是一个老问题,已经给出了答案,但我今天偶然发现了这个问题并发现当前版本的FluentValidation(我正在使用6.2.1.0)对 SetValidator ,允许您将Func作为参数传递。
所以,你可以这样做:
RuleForEach(x => x.CollectionProperty)
// x below will referente the Parent class
.SetValidator(x => new CollectionItemValidator(x.ParentProperty);
希望这可以帮助那些人。
答案 2 :(得分:1)
使用Custom方法的方法如何:
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()
{
}
}