简单注入器和 Fluent 验证的通用协方差

Generic covariance with Simple Injector and Fluent Validation

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

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

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

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

public class QueryValidationHandler : IQueryValidationHandler<IQuery>{ }

class 中可用的查询是一种具体类型的 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);

我收到以下错误:

The constructor of type QueryValidationHandler contains the parameter of type IValidator<IQuery> with name 'validator' that is not registered. Please ensure IValidator<IQuery> is registered in the container, or change the constructor of QueryValidationHandler.

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

更新

在史蒂文的出色回答之后,我仍然卡住了(同样的例外)。很可能是因为我正在尝试将 Fluent Validators AbstractValidator< T > 的实现注册为 IValidator< T >。在以前的项目中,我能够进行以下注册:

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

对于以下实现:

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

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

出现异常是因为您从未注册过IValidator<T>;您只注册了 IEnumerable<Validator<T>>。来自 'normal' 个注册的简单注射器 differentiates registration of collections

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

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

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

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

// 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> 的验证器之一将是 CompositeValidator<T>本身,最终会导致堆栈溢出异常。

您的查询处理程序现在可以再次简单地依赖于 IValidator<T>:

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

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

更新:

因为你有一个非泛型 QueryValidationHandler 应该能够验证任何查询,注入 IValidator<IQuery> 将不起作用,因为你的 DI 库不知道什么验证器实现它需要注入。相反,您需要使用可以将验证委托给真正的验证器的中介。例如:

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 注入。此验证器会将调用转发给可能存在的任何 IValidator<T> 实现。