2017-07-08 86 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。由於我們使用了命令和查詢模式,因此我們非常依賴此功能。 –

相關問題