出现异常是因为您从未注册过IValidator<T>;您只注册了IEnumerable<Validator<T>>。来自“正常”注册的简单注入器 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> 的每个封闭通用版本可能有一个实现(或没有)?
- 一对多映射,每个封闭的通用版本有零个、一个或多个实现?
对于每种类型,您都需要进行不同的注册。对于一对一,您需要以下内容:
// 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> 实现。