如何使用FluentValidation验证集合中的不同类型? [英] How can I validate different types within a collection using FluentValidation?

查看:387
本文介绍了如何使用FluentValidation验证集合中的不同类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a class with a collection that needs validation. The generic on the collection takes an interface and different types can be added to the collection.

创建支持多态的FluentValidation验证器的最干净的方法是什么?

What is the cleanest path forward to creating a FluentValidation validator that supports polymorphism?

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在您的规则定义中.

FluentValidation doesn't support polymorphism for child collections like this out of the box, but you can add this behaviour by using a custom property validator, or by using OfType in your rule definitions.

在此之前,我已经写过这两种方法:

首先为WizardOne和WizardTwo创建验证器:

Start by creating a validator for WizardOne and 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,但是性能较低.更为复杂的选项是使用自定义属性验证器.

Step 2: Create the parent validator

You have two options for defining the parent validator. The simplest approach is to use OfType, but this is less performant. The more complex option is to use a custom property validator.

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的表达式缓存,这可能会打击性能.它还会迭代收集多个对象.对您而言,这可能是问题,也可能不是问题-您需要确定这是否会对您的应用程序产生现实影响.

This is the simplest approach, but calling OfType inside the call RuleFor will end up bypassing FluentValidation's expression cache, which is a potential performance hit. It also iterates the collection multiple. This may or may not be an issue for you - you'll need to decide if this has any real-world impact on your application.

这使用了一个自定义的自定义验证器,该验证器可以在运行时区分基础类型:

This uses a custom custom validator which can differentiate the underlying type at runtime:

public WizardValidator : AbstractValidator<Wizard> {
  public WizardValidator() {
    RuleForEach(x => x.Steps).SetValidator(new PolymorphicValidator<Wizard, IWizardStep>()
      .Add<WizardOne>(new WizardOneValidator())
      .Add<WizardTwo>(new WizardTwoValidator())
    );
  }
}

从语法上讲,这不是很好,但是不会绕过表达式缓存,也不会多次迭代集合.这是PolymorphicValidator:

Syntactically, this isn't quite as nice, but doesn't bypass the expression cache and doesn't iterate the collection multiple times. This is the code for the 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();
        }
    }
}

将来可能会在库中将其作为一流功能实现-如果您有兴趣,可以在这里跟踪其发展情况.

This will probably be implemented in the library as a first class feature at some point in the future - you can track its development here if you're interested.

这篇关于如何使用FluentValidation验证集合中的不同类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆