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

问题

在依赖于 Microsoft.Extensions.DependencyInjection 1.1.1 的 ASP.NET Core 1.1.2 Web 项目中,我正在尝试注册一个通用的 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 UserId 和一个 T Content)。

注册是这样的:

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

// 这是用于顶级命令验证器的,由接口注入
services.AddScoped(someInterface, someImplementation);

// 这是嵌套的验证器,由实现注入
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 上寻找类似的问题和问题,但它们并不真正相关,因为它们是关于通用实现的,而这是一个具体的实现。

我单步执行了 DependencyInjection 模块代码,这里抛出了异常。 出于某种原因,serviceTypeInfo.IsGenericTypeDefinition 返回 false(这不应该是真的,因为接口类型是通用的吗?)因此采用了 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}

在调试时,我尝试只注册实现,而不注册接口+实现,然后那个异常就消失了。 但如果可能的话,我想注入接口,而不是实现。

答案

您要做的是映射一个部分封闭的类型(即 IValidator<WithUser<T>>),这是 .NET Core 的 DI 容器不支持的。 对于泛型类型,.NET Core 容器是一个非常简约、简单的实现。 它无法处理类似的事情:

  • 通用类型约束
  • 部分封闭类型
  • 具有比抽象更少泛型类型参数的实现
  • 具有与抽象顺序不同的泛型类型参数的实现
  • 将泛型类型参数嵌套为部分封闭抽象的类型参数的一部分(这是您的确切情况)
  • 奇怪的重复模板模式

如果您需要此行为并希望为此继续使用内置容器,则必须手动注册每个关闭的实现:

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值