我正在构建一个查询管道(使用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实现,因此它可以检查错误并在验证失败时抛出异常。
答案 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
实现。