Voc*_*cte 2 c# contravariance fluentvalidation simple-injector
我正在构建一个查询管道(使用IQueryHandler的装饰器模式),其中,在实际执行查询之前,处理了许多横切关注点.其中一个问题是验证,我正在使用Fluent验证库.
Simple Injector是IoC容器的首选,我使用它来使用通用的逆变和以下注册来注册每个IQueryValidationHandler:
container.RegisterManyForOpenGeneric(typeof(IQueryValidationHandler<>), container.RegisterAll, _assemblies);
Run Code Online (Sandbox Code Playgroud)
这非常适合解决以下实现:
public class QueryValidationHandler : IQueryValidationHandler<IQuery>{ }
Run Code Online (Sandbox Code Playgroud)
该类中可用的查询是一种具体的IQuery类型.但是,当我尝试将基于IQuery的IValidator(通过构造函数注入)注入QueryValidationHandler时,使用以下代码和注册:
码:
public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
private IValidator< IQuery > _validator;
public QueryValidationHandler(IValidator<IQuery> validator)
{
_validator = validator;
}
Run Code Online (Sandbox Code Playgroud)
注册:
container.RegisterManyForOpenGeneric(typeof (IValidator<>), container.RegisterAll, _assemblies);
Run Code Online (Sandbox Code Playgroud)
我收到以下错误:
QueryValidationHandler类型的构造函数包含IValidator <IQuery>类型的参数,其名称为"validator",未注册.请确保在容器中注册了IValidator <IQuery>,或者更改了QueryValidationHandler的构造函数.
有没有办法为注入QueryValidationHandler的具体类型的IQuery获取IValidator的实现?
更新
在史蒂文的出色回答之后,我仍然陷入困境(同样的例外).很可能是因为我试图将Fluent Validators AbstractValidator <T>的实现注册为IValidator <T>.在之前的项目中,我能够进行以下注册:
container.RegisterManyForOpenGeneric(typeof(IValidator<>), typeof(FindProductsForCompanyQuery).Assembly);
Run Code Online (Sandbox Code Playgroud)
对于以下实现:
public class FindProductsForCompanyQueryValidator : AbstractValidator<FindProductsForCompanyQuery>
{
public FindProductsForCompanyQueryValidator()
{
RuleFor(q => q.CompanyId).NotNull();
}
}
Run Code Online (Sandbox Code Playgroud)
上面提到的QueryValidationHandler应该注入程序集中的所有AbstractValidator实现,因此它可以检查错误并在验证失败时抛出异常.
发生异常是因为您从未进行过注册IValidator<T>
; 你只是为了注册IEnumerable<Validator<T>>
.Simple Injector 将集合注册与"正常"注册区分开来.
由于您要求IValidator<IQuery>
,简单注入器期望有注册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);
Run Code Online (Sandbox Code Playgroud)
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<>));
Run Code Online (Sandbox Code Playgroud)
在这里,我们注册一个开放的通用EmptyValidator<T>
作为后备注册,这意味着如果没有明确的注册,它将被选中.
第三种情况,即一对多,是您已经使用的:
// Simple Injector v3.x
container.RegisterCollection(typeof(IValidator<>), _assemblies);
// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>),
container.RegisterAll, _assemblies);
Run Code Online (Sandbox Code Playgroud)
但是如果你注册一个集合,你需要注入一个枚举,或者你需要创建一个复合验证器.
注入IEnumerable的示例:
public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
private IEnumerable<IValidator<IQuery>> _validators;
public QueryValidationHandler(IEnumerable<IValidator<IQuery>> validators) {
_validators = validators;
}
}
Run Code Online (Sandbox Code Playgroud)
复合验证器非常有用,尤其是当您有多个类需要注入这些验证器时.在这种情况下,您不希望应用程序知道存在多个验证器(这是一个实现细节)的事实,就像循环所有验证器一样,也是一个实现细节; 它违反了依赖性倒置原则.此外,它会验证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;
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个,CompositeValidator<T>
您将获得以下注册:
container.RegisterCollection(typeof(IValidator<>), _assemblies);
container.Register(typeof(IValidator<>), typeof(CompositeValidator<>),
Lifestyle.Singleton);
Run Code Online (Sandbox Code Playgroud)
这是Simple Injector设计的众多优势之一,其中集合的注册与一对一注册分开.它使注册复合材料变得非常简单.对于没有这种清晰分离的容器来注册这样的复合很难,因为对于那些容器,没有对你的配置做任何特殊处理,注入到CompositeValidator<T>
will中的一个验证器CompositeValidator<T>
就是它自己,这最终会导致堆栈溢出例外.
您的查询处理程序现在可以再次依赖于IValidator<T>
:
public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
private IValidator<IQuery> _validator;
public QueryValidationHandler(IValidator<IQuery> validator) {
_validator = validator;
}
}
Run Code Online (Sandbox Code Playgroud)
更新:
由于你有一个非通用的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);
}
}
Run Code Online (Sandbox Code Playgroud)
您的消费者现在可以依赖非通用IValidator
接口并获得Validator
注入.此验证器将调用转发给IValidator<T>
可能存在的任何实现.