FluentValidation-验证接口子属性

问题描述

我正在尝试使用访客模式在树结构上应用FluentValidation(v 9.1.1)。它的特殊之处在于,几个不同的树元素都实现了一个接口,并且这些元素的子属性属于此接口类型。换句话说,子属性不是强类型的。简化模型请参见下文。每个验证器都在特定的实现上,我不明白如何为 interface 子代附加子验证器。

这是一个演示模型(工作代码):

public void OnTriggerEnter(Collider other)
{
    if (other.gameObject.tag == "Ballon")
    {
         TextMeshPro tmp = other.transform.GetChild(0).GetComponent<TextMeshPro>();
         if(tmp.text == "text of your liking")
         {
             Debug.Log("Do what you want");
         }
    }
 }

通常,有两种添加子验证的方法。或者直接在验证器中调用子验证器,这会使ValidationVisitor过时,或者让验证器专注于自己的逻辑,并在ValidationVisitor中添加子验证,如代码所示。

我现在唯一能够进行的方法是使用访问者,并合并一个元素及其子元素的验证结果。

在这种情况下,是否可以将子验证器添加到BinaryElement?要么在访问者中,要么直接在BinaryElementValidator中。

解决方法

有两种不同的方法可以做到这一点。您可以为每个接口实现器定义多个规则,也可以使用自定义属性验证器对类型进行运行时检查。这类似于this answer

选项1:使用类型过滤器的多个规则定义

使用此选项,可以为接口的每个潜在实现者创建一个特定的规则定义:

// Inside your BinaryElementValidator use a safe cast inside the RuleFor definition. 
// If it isn't the right type,the child validator won't be executed 
// as child validators aren't run for null properties.

RuleFor(x => x.Left as BinaryElement).SetValidator(new BinaryElementValidator());
RuleFor(x => x.Left as ConstElement).SetValidator(new ConstElementValidator());

RuleFor(x => x.Right as BinaryElement).SetValidator(new BinaryElementValidator());
RuleFor(x => x.Right as ConstElement).SetValidator(new ConstElementValidator());


这是最简单的方法,但是通过在对RuleFor的调用中包含更复杂的表达式,您将绕过FluentValidation的表达式缓存,如果您多次实例化验证程序,这将对性能造成影响。我让您来决定这是否会成为您应用程序中的问题。

您可能还需要为每个规则调用OverridePropertyName,因为FluentValidation无法使用这种方法来推断属性的名称。

选项2:自定义属性验证器

稍微复杂一点的解决方案,但意味着您可以在RuleFor中使用简单的属性表达式,这意味着您不会绕过缓存。这利用了称为PolymorphicValidator的自定义验证器,它将在运行时检查属性的类型。

RuleFor(x => x.Left).SetValidator(new PolymorphicValidator<BinaryElement,IElement>()
  .Add<BinaryElement>(new BinaryElementValidator())
  .Add<ConstElement>(new ConstElementValidator())
);

RuleFor(x => x.Right).SetValidator(new PolymorphicValidator<BinaryElement,IElement>()
  .Add<BinaryElement>(new BinaryElementValidator())
  .Add<ConstElement(new ConstElementValidator())
);

这是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();
        }
    }
}

此方法实际上将在将来的版本中添加到库中-如果您有兴趣,可以在这里跟踪其发展:https://github.com/FluentValidation/FluentValidation/issues/1237