如何使用FluentValidation验证集合中的不同类型? [英] How can I validate different types within a collection using 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屋!