2017-07-08 64 views
2

在ASP.NET Core 1.1.2 web项目中,这取决于Microsoft.Extensions.DependencyInjection 1.1.1,我试图注册一个通用的FluentValidation验证器与它的实现和它的接口。在运行时,主机建设过程中,我得到了以下异常:使用ASP.NET Core注册部分关闭的泛型类型Microsoft.Extensions.DependencyInjection

An unhandled exception of type 'System.ArgumentException' occurred in Microsoft.Extensions.DependencyInjection.dll 

Cannot instantiate implementation type 'MyProj.Shared.Api.WithUserCommandValidator`1[T]' for service type 'FluentValidation.IValidator`1[MyProj.Shared.Model.WithUser`1[T]]'. 

    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceTable..ctor(IEnumerable`1 descriptors) 
    at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, Boolean validateScopes) 
    at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services) 
    at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() 
    at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build() 
    at MyProj.Program.Main(String[] args) in X:\MySolution\src\MyProj\Program.cs:line 31 

这是(通用,非抽象)的验证器类:

public class WithUserCommandValidator<TCommand> : AbstractValidator<WithUser<TCommand>> 
    where TCommand : ICommand 
{ 
    public WithUserCommandValidator(IValidator<TCommand> commandValidator) 
    { 
     RuleFor(cmd => cmd).NotNull(); 

     RuleFor(cmd => cmd.UserId).NotEqual(Guid.Empty); 

     RuleFor(cmd => cmd.Content).NotNull() 
      .SetValidator(commandValidator); 
    } 
} 

(不知道是很重要的,以显示WithUser<T>类,它只是持有Guid UserIdT Content)。

注册偏偏喜欢这样:

someInterface = typeof(FluentValidation.IValidator<WithUser<>>); 
someImplementation = typeof(Shared.Api.WithUserCommandValidator<>); 

// this is for top-level command validators, injected by interface 
services.AddScoped(someInterface, someImplementation); 

// this is for nested validators, injected by implementation 
services.AddScoped(someImplementation); 

,并在运行时这些变量持有:

+ someInterface {FluentValidation.IValidator`1[MyProj.Shared.Model.WithUser`1[TCommand]]} System.Type {System.RuntimeType} 
+ someImplementation {MyProj.Shared.Api.WithUserCommandValidator`1[TCommand]} System.Type {System.RuntimeType} 

这似乎是正确的。

我也在SO上寻找类似的问题和疑问,但它们并没有真正相关,因为它们是关于泛型实现的,而这是一个具体实现。

我已经通过了依赖注入模块代码,并抛出异常here。由于某种原因,serviceTypeInfo.IsGenericTypeDefinition返回false(不应该是true,因为接口类型是通用的?),因此采用else分支。在那里,相反,实现类型返回trueimplementationTypeInfo.IsGenericTypeDefinition,所以你有例外。

运行值:

+ serviceTypeInfo {FluentValidation.IValidator`1[MyProj.Shared.Model.WithUser`1[TCommand]]} System.Reflection.TypeInfo {System.RuntimeType} 
+ implementationTypeInfo {MyProj.Shared.Api.WithUserCommandValidator`1[TCommand]} System.Reflection.TypeInfo {System.RuntimeType} 

虽然摆弄,我试过只注册实施,也没有注册接口+实现,而且异常消失。但是如果可能的话,我想注入接口,而不是实现。

我在做什么错? TA

+0

一些更详细信息:.NET的核心代码检查'serviceTypeInfo.IsGenericTypeDefinition',这是假的,而不是'serviceTypeInfo.IsGenericType'是真实的。但是,我不知道前者是什么意思。 – superjos

回答

2

你要做的是映射一个局部封闭类型(即IValidator<WithUser<T>>),这是.NET Core的DI容器不支持的东西。当涉及到泛型类型时,.NET Core容器是一个非常简约,简单的实现。它无法处理之类的东西:

  • 通用类型约束
  • 部分闭合类型
  • 实现与通用类型的参数比抽象少
  • 实现与通用类型参数按照不同的顺序比所述抽象
  • 将泛型类型参数嵌套为部分关闭的抽象类型参数的一部分(这是您的确切情况)
  • Curiously recurring template pattern

如果您需要此行为想保持继续使用内置的这个容器,你必须注册通过手中的每个封闭的实现:

AddScoped(typeof(IValidator<WithUser<Cmd1>>), typeof(WithUserCommandValidator<Cmd1>)); 
AddScoped(typeof(IValidator<WithUser<Cmd2>>), typeof(WithUserCommandValidator<Cmd2>)); 
AddScoped(typeof(IValidator<WithUser<Cmd3>>), typeof(WithUserCommandValidator<Cmd3>)); 
// etc 

如果这是导致一个不可维护的组合根,你应该选择一个不同的容器。然而,你的选择在这方面是有限的。我唯一知道的DI容器实际上完全支持这些类型的通用设计是Simple Injector

有了简单喷油器,你可以简单地做以下注册:

container.Register(
    typeof(IValidator<>), 
    typeof(WithUserCommandValidator<>), 
    Lifestyle.Scoped); 
+0

我明白了。没有意识到这些具体的限制。谢谢 – superjos

+1

Autofac还能够注册这些开放类型:http://autofaccn.readthedocs.io/en/latest/register/registration.html#open-generic-components。由于我们使用了命令和查询模式,因此我们非常依赖此功能。 –

相关问题