简单注入器和 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>
实现。
我正在构建一个查询管道(使用 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>
实现。