使用ASP.NET Core Microsoft.Extensions.DependencyInjection注册部分关闭的泛型类型

问题描述 投票:3回答:1

在ASP.NET Core 1.1.2 Web项目中,它依赖于Microsoft.Extensions.DependencyInjection 1.1.1,我试图通过其实现和接口注册一个通用的FluentValidation验证器。在运行时,在主机构建期间,我遇到以下异常:

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}

这似乎是正确的。

我也在环顾四周寻找类似的问题和问题,但它们并没有真正相关,因为它们是关于通用实现的,而这是一个具体的问题。

我已经通过DependencyInjection模块代码,并抛出here异常。由于某种原因,serviceTypeInfo.IsGenericTypeDefinition返回false(不应该是真的,因为接口类型是通用的?)因此采用了else-branch。相反,实现类型返回true到implementationTypeInfo.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

c# generics dependency-injection asp.net-core .net-core
1个回答
3
投票

你要做的是映射一个部分封闭的类型(即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

使用Simple Injector,您只需执行以下注册:

container.Register(
    typeof(IValidator<>),
    typeof(WithUserCommandValidator<>),
    Lifestyle.Scoped);
© www.soinside.com 2019 - 2024. All rights reserved.