如何使用 FluentValidation 验证集合中的不同类型?
How can I validate different types within a collection using FluentValidation?
我有一个 class 的集合需要验证。集合上的泛型采用接口,可以将不同的类型添加到集合中。
创建支持多态性的 FluentValidation 验证器的最简洁路径是什么?
public interface IWizardStep {}
public class WizardOne : IWizardStep
{
public string Model { get; set; }
}
public class WizardTwo : IWizardStep
{
public string FirstName { get; set; }
}
public class Wizard
{
public Wizard()
{
var w1 = new WizardOne();
var w2 = new WizardTwo();
Steps = new List<IWizardStep>
{
w1,
w2
};
}
public IList<IWizardStep> Steps { get; set; }
}
public class WizardValidator : AbstractValidator<Wizard>
{
public WizardValidator()
{
RuleFor(x => x.Steps)
// Steps First where is WizardOne
// Model.NotEmpty()
// Steps First where is WizardTwo
// FirstName.NotEmpty()
}
FluentValidation 不支持开箱即用的子集合的多态性,但您可以通过使用自定义 属性 验证器或在规则定义中使用 OfType
添加此行为.
I've written about both approaches before here:
第 1 步:为每个实施者创建一个验证器
首先为 WizardOne 和 WizardTwo 创建一个验证器:
public class WizardOneValidator : AbstractValidator<WizardOne> {
public WizardOneValidator() {
RuleFor(x => x.Model).NotEmpty();
}
}
public class WizardTwoValidator : AbstractValidator<WizardTwo> {
public WizardTwoValidator() {
RuleFor(x => x.FirstName).NotEmpty();
}
}
第 2 步:创建父验证器
您有两种选择来定义父验证器。最简单的方法是使用 OfType
,但性能较差。更复杂的选项是使用自定义 属性 验证器。
选项 1:使用 OfType
public WizardValidator : AbstractValidator<Wizard> {
public WizardValidator() {
RuleForEach(x => x.Steps.OfType<WizardOne>()).SetValidator(new WizardOneValidator());
RuleForEach(x => x.Steps.OfType<WizardTwo>()).SetValidator(new WizardTwoValidator());
}
}
这是最简单的方法,但在调用 RuleFor
中调用 OfType
最终会绕过 FluentValidation 的表达式缓存,这可能会影响性能。它还多次迭代集合。这对您来说可能是问题,也可能不是问题 - 您需要决定这是否对您的申请有任何 real-world 影响。
选项 2:使用自定义 PropertyValidator。
这使用自定义验证器,它可以在运行时区分底层类型:
public WizardValidator : AbstractValidator<Wizard> {
public WizardValidator() {
RuleForEach(x => x.Steps).SetValidator(new PolymorphicValidator<Wizard, IWizardStep>()
.Add<WizardOne>(new WizardOneValidator())
.Add<WizardTwo>(new WizardTwoValidator())
);
}
}
从语法上讲,这不是很好,但不会绕过表达式缓存,也不会多次迭代集合。这是 PolymorphicValidator
:
的代码
public class PolymorphicValidator<T, TInterface> : ChildValidatorAdaptor<T, TInterface> {
readonly Dictionary<Type, IValidator> _derivedValidators = new Dictionary<Type, IValidator>();
// Need the base constructor call, even though we're just passing null.
public PolymorphicValidator() : base((IValidator<TInterface>)null, typeof(IValidator<TInterface>)) {
}
public PolymorphicValidator<T, TInterface> Add<TDerived>(IValidator<TDerived> derivedValidator) where TDerived : TInterface {
_derivedValidators[typeof(TDerived)] = derivedValidator;
return this;
}
public override IValidator<TInterface> GetValidator(PropertyValidatorContext context) {
// bail out if the current item is null
if (context.PropertyValue == null) return null;
if (_derivedValidators.TryGetValue(context.PropertyValue.GetType(), out var derivedValidator)) {
return new ValidatorWrapper(derivedValidator);
}
return null;
}
private class ValidatorWrapper : AbstractValidator<TInterface> {
private IValidator _innerValidator;
public ValidatorWrapper(IValidator innerValidator) {
_innerValidator = innerValidator;
}
public override ValidationResult Validate(ValidationContext<TInterface> context) {
return _innerValidator.Validate(context);
}
public override Task<ValidationResult> ValidateAsync(ValidationContext<TInterface> context, CancellationToken cancellation = new CancellationToken()) {
return _innerValidator.ValidateAsync(context, cancellation);
}
public override IValidatorDescriptor CreateDescriptor() {
return _innerValidator.CreateDescriptor();
}
}
}
这可能会在未来的某个时候作为第一个 class 功能在库中实现 - you can track its development here if you're interested。
我有一个 class 的集合需要验证。集合上的泛型采用接口,可以将不同的类型添加到集合中。
创建支持多态性的 FluentValidation 验证器的最简洁路径是什么?
public interface IWizardStep {}
public class WizardOne : IWizardStep
{
public string Model { get; set; }
}
public class WizardTwo : IWizardStep
{
public string FirstName { get; set; }
}
public class Wizard
{
public Wizard()
{
var w1 = new WizardOne();
var w2 = new WizardTwo();
Steps = new List<IWizardStep>
{
w1,
w2
};
}
public IList<IWizardStep> Steps { get; set; }
}
public class WizardValidator : AbstractValidator<Wizard>
{
public WizardValidator()
{
RuleFor(x => x.Steps)
// Steps First where is WizardOne
// Model.NotEmpty()
// Steps First where is WizardTwo
// FirstName.NotEmpty()
}
FluentValidation 不支持开箱即用的子集合的多态性,但您可以通过使用自定义 属性 验证器或在规则定义中使用 OfType
添加此行为.
I've written about both approaches before here:
第 1 步:为每个实施者创建一个验证器
首先为 WizardOne 和 WizardTwo 创建一个验证器:
public class WizardOneValidator : AbstractValidator<WizardOne> {
public WizardOneValidator() {
RuleFor(x => x.Model).NotEmpty();
}
}
public class WizardTwoValidator : AbstractValidator<WizardTwo> {
public WizardTwoValidator() {
RuleFor(x => x.FirstName).NotEmpty();
}
}
第 2 步:创建父验证器
您有两种选择来定义父验证器。最简单的方法是使用 OfType
,但性能较差。更复杂的选项是使用自定义 属性 验证器。
选项 1:使用 OfType
public WizardValidator : AbstractValidator<Wizard> {
public WizardValidator() {
RuleForEach(x => x.Steps.OfType<WizardOne>()).SetValidator(new WizardOneValidator());
RuleForEach(x => x.Steps.OfType<WizardTwo>()).SetValidator(new WizardTwoValidator());
}
}
这是最简单的方法,但在调用 RuleFor
中调用 OfType
最终会绕过 FluentValidation 的表达式缓存,这可能会影响性能。它还多次迭代集合。这对您来说可能是问题,也可能不是问题 - 您需要决定这是否对您的申请有任何 real-world 影响。
选项 2:使用自定义 PropertyValidator。
这使用自定义验证器,它可以在运行时区分底层类型:
public WizardValidator : AbstractValidator<Wizard> {
public WizardValidator() {
RuleForEach(x => x.Steps).SetValidator(new PolymorphicValidator<Wizard, IWizardStep>()
.Add<WizardOne>(new WizardOneValidator())
.Add<WizardTwo>(new WizardTwoValidator())
);
}
}
从语法上讲,这不是很好,但不会绕过表达式缓存,也不会多次迭代集合。这是 PolymorphicValidator
:
public class PolymorphicValidator<T, TInterface> : ChildValidatorAdaptor<T, TInterface> {
readonly Dictionary<Type, IValidator> _derivedValidators = new Dictionary<Type, IValidator>();
// Need the base constructor call, even though we're just passing null.
public PolymorphicValidator() : base((IValidator<TInterface>)null, typeof(IValidator<TInterface>)) {
}
public PolymorphicValidator<T, TInterface> Add<TDerived>(IValidator<TDerived> derivedValidator) where TDerived : TInterface {
_derivedValidators[typeof(TDerived)] = derivedValidator;
return this;
}
public override IValidator<TInterface> GetValidator(PropertyValidatorContext context) {
// bail out if the current item is null
if (context.PropertyValue == null) return null;
if (_derivedValidators.TryGetValue(context.PropertyValue.GetType(), out var derivedValidator)) {
return new ValidatorWrapper(derivedValidator);
}
return null;
}
private class ValidatorWrapper : AbstractValidator<TInterface> {
private IValidator _innerValidator;
public ValidatorWrapper(IValidator innerValidator) {
_innerValidator = innerValidator;
}
public override ValidationResult Validate(ValidationContext<TInterface> context) {
return _innerValidator.Validate(context);
}
public override Task<ValidationResult> ValidateAsync(ValidationContext<TInterface> context, CancellationToken cancellation = new CancellationToken()) {
return _innerValidator.ValidateAsync(context, cancellation);
}
public override IValidatorDescriptor CreateDescriptor() {
return _innerValidator.CreateDescriptor();
}
}
}
这可能会在未来的某个时候作为第一个 class 功能在库中实现 - you can track its development here if you're interested。