简单注射器和流畅验证的通用协方差

时间:2015-02-14 09:35:27

标签: c# fluentvalidation simple-injector contravariance

我正在构建一个查询管道(使用IQueryHandler的装饰器模式),在该管道中,在实际执行查询之前,会处理许多横切关注点。其中一个问题是验证,我正在使用Fluent验证库。

Simple Injector是首选的IoC容器,我使用它来使用通用的逆变法注册每个IQueryValidationHandler并进行以下注册:

container.RegisterManyForOpenGeneric(typeof(IQueryValidationHandler<>), container.RegisterAll, _assemblies);

这非常适合解决以下实现:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>{ }

该类中可用的查询是一种具体类型的IQuery。但是,当我尝试将基于IQuery的IValidator(通过构造函数注入)注入QueryValidationHandler时,使用以下代码并注册:

代码:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
    private IValidator< IQuery > _validator;

    public QueryValidationHandler(IValidator<IQuery> validator)
    {
        _validator = validator;
    }

注册

container.RegisterManyForOpenGeneric(typeof (IValidator<>), container.RegisterAll, _assemblies);

我收到以下错误:

  

QueryValidationHandler类型的构造函数包含IValidator类型的参数&lt; IQuery&gt;名称为'validator'且未注册。请确保IValidator&lt; IQuery&gt;在容器中注册,或更改QueryValidationHandler的构造函数。

有没有办法为注入QueryValidationHandler的具体类型的IQuery获取IValidator的实现?

更新

在史蒂文的出色回答之后,我仍然陷入困境(同样的例外)。很可能是因为我正在尝试注册Fluent Validators的实现AbstractValidator&lt; T>作为IValidator&lt; T>。在之前的项目中,我能够进行以下注册:

container.RegisterManyForOpenGeneric(typeof(IValidator<>), typeof(FindProductsForCompanyQuery).Assembly);

对于以下实现:

public class FindProductsForCompanyQueryValidator : AbstractValidator<FindProductsForCompanyQuery>
{
    public FindProductsForCompanyQueryValidator()
    {
        RuleFor(q => q.CompanyId).NotNull();
    }
}

上面提到的QueryValidationHandler应该注入程序集中的所有AbstractValidator实现,因此它可以检查错误并在验证失败时抛出异常。

1 个答案:

答案 0 :(得分:6)

发生异常是因为您从未对IValidator<T>进行注册;您只对IEnumerable<Validator<T>>进行了注册。来自&#39; normal&#39;的简单注射器differentiates registration of collections注册

由于您请求IValidator<IQuery>,因此Simple Injector希望注册IValidator<IQuery>,通常使用Register<IValidator<IQuery>, SomeImplementation>()注册。但是,您注册了IValidator<T>的集合(请注意container.RegisterAll中的RegisterManyForOpenGeneric),这允许Simple Injector注入集合。

这里真正的问题当然是:您需要什么类型的注册? IValidator<T>应该有哪种映射?你需要一个:

  • 一对一映射,每个封闭的通用版本IValidator<T>只有一个实现?
  • 一对一或一对映射,其中每个封闭的通用版本IValidator<T>可能有一个实现(或没有)?
  • 一对多映射,每个封闭的通用版本都有零个,一个或多个实现?

对于每种类型,您需要不同的注册。对于一对一,您需要以下内容:

// Simple Injector v3.x
container.Register(typeof(IValidator<>), _assemblies);

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>), _assemblies);

传递程序集列表时Register的默认行为是在每个找到的实现上调用Register(serviceType, ImplementationType)。由于Simple Injector不允许对同一服务类型进行多次注册(因为它是RegisterCollection的用途),如果您(意外地)具有相同的封闭泛型类型的第二个验证器,则注册将失败。这是一个非常有用的安全措施。

然而,通过验证,通常只对一些查询进行验证。并非所有查询都需要具有特定的验证器。使用一对一映射会非常烦人,因为您必须为每个没有任何验证的查询定义许多空验证器。对于这种情况,Simple Injector允许您注册将在没有注册的情况下选择的后备注册:

// Simple Injector v3.x
container.Register(typeof(IValidator<>), _assemblies);
container.RegisterConditional(typeof(IValidator<>), typeof(EmptyValidator<>),
    c => !c.Handled);

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>), _assemblies);
container.RegisterOpenGeneric(typeof(IValidator<>), typeof(EmptyValidator<>));

在这里,我们注册一个开放的通用EmptyValidator<T>作为后备注册,这意味着如果没有明确的注册,它将会被选中。

第三种情况,一对多,就是你已经使用的:

// Simple Injector v3.x
container.RegisterCollection(typeof(IValidator<>), _assemblies);

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>),
    container.RegisterAll, _assemblies);

但是如果你注册一个集合,你需要注入一个枚举,或者你需要创建一个复合验证器。

注入IEnumerable的示例:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
    private IEnumerable<IValidator<IQuery>> _validators;

    public QueryValidationHandler(IEnumerable<IValidator<IQuery>> validators) {
        _validators = validators;
    }
}

复合验证器非常有用,尤其是当您有多个类需要注入这些验证器时。在这种情况下,您不希望应用程序知道存在多个验证器(这是一个实现细节)的事实,就像循环所有验证器一样,也是一个实现细节;它违反了依赖性倒置原则。此外,它会验证DRY,因为您正在复制代码。总是一件坏事。

所以你可以做的是创建一个复合词:

public class CompositeValidator<T> : IValidator<T>
{
    private IEnumerable<IValidator<T>> _validators;
    public CompositeValidator(IEnumerable<IValidator<T>> validators) {
        _validators = validators;
    }

    public IEnumerable<ValidationError> Validate(T instance) {
        return
            from validator in _validators
            from error in validator.Validate(instance)
            select error;
    }
}

使用此CompositeValidator<T>,您将获得以下注册:

container.RegisterCollection(typeof(IValidator<>), _assemblies);
container.Register(typeof(IValidator<>), typeof(CompositeValidator<>),
    Lifestyle.Singleton);

这是Simple Injector设计的众多优势之一,其中集合的注册与一对一注册分开。它使注册复合材料变得非常简单。对于没有这种清晰分离的容器来说,注册这样的复合材料要困难得多,因为对于那些容器,没有对配置做任何特殊处理,注入CompositeValidator<T>的验证器之一将是{{1本身,最终会导致堆栈溢出异常。

您的查询处理程序现在可以再次依赖于CompositeValidator<T>

IValidator<T>

<强>更新

由于您有一个非通用public class QueryValidationHandler : IQueryValidationHandler<IQuery> { private IValidator<IQuery> _validator; public QueryValidationHandler(IValidator<IQuery> validator) { _validator = validator; } } 应该能够验证任何查询,因此注入QueryValidationHandler将不起作用,因为您的DI库不知道它需要什么验证器实现注入。相反,您需要使用可以将验证委派给真实验证器的中介。例如:

IValidator<IQuery>

您的消费者现在可以依赖非通用sealed class Validator : IValidator // note: non-generic interface { private readonly Container container; public Validator(Container container) { this.container = container; } [DebuggerStepThrough] public IEnumerable<ValidationError> Validate(object instance) { var validators = container.GetAllInstances( typeof(IValidator<>).MakeGenericType(instance.GetType())); return from validator in validators from error in Validate(validator, instance) select error; } private static IEnumerable<ValidationError> Validate( dynamic validator, dynamic instance) { return validator.Validate(instance); } } 界面并注入IValidator。此验证程序会将调用转发给可能存在的任何Validator实现。