【问题标题】:How can I validate different types within a collection using FluentValidation?如何使用 FluentValidation 验证集合中的不同类型?
【发布时间】:2020-08-12 14:30:06
【问题描述】:

我有一个包含需要验证的集合的类。集合上的泛型需要一个接口,并且可以将不同的类型添加到集合中。

创建支持多态性的 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


    【解决方案1】:

    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 的表达式缓存,这可能会影响性能。它还对集合进行多次迭代。这对您来说可能是也可能不是问题 - 您需要确定这是否会对您的应用程序产生任何实际影响。

    选项 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();
            }
        }
    }
    

    这可能会在未来某个时候作为一流功能在库中实现 - you can track its development here if you're interested

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-09
      • 2019-04-22
      • 2012-12-08
      • 2019-04-13
      相关资源
      最近更新 更多