问题描述
我正在尝试使用访客模式在树结构上应用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