在 ASP.NET Core 中使用 Autofac 注册泛型类型

Issue registering generic types with Autofac in ASP.NET Core

我是 Autofac 和 ASP.NET Core 的新用户。我最近将一个小项目从 'classic' ASP.NET WebAPI 项目移植到 ASP.NET Core。我在使用 Autofac 时遇到问题,特别是在泛型类型的注册方面。

这个项目使用命令模式,每个命令处理程序都是一个封闭的泛型,如

public class UpdateCustomerCommandHandler: ICommandHandler<UpdateCustomerCommand>

这些命令处理程序被注入控制器,如:

readonly private ICommandHandler<UpdateCustomerCommand> _updateCustomerCommand;
public ValuesController(ICommandHandler<UpdateCustomerCommand> updateCustomerCommand)
{
    _updateCustomerCommand = updateCustomerCommand;
}

Autofac 配置(部分)为:

 var builder = new ContainerBuilder();
 var assemblies = AppDomain.CurrentDomain.GetAssemblies();
 //This doesn't seem to be working as expected.
 builder.RegisterAssemblyTypes(assemblies)
     .As(t => t.GetInterfaces()
         .Where(a => a.IsClosedTypeOf(typeof(ICommandHandler<>)))
         .Select(a => new KeyedService("commandHandler", a)));

以上似乎没有按预期注册泛型。如果我使用下面的方法注册,效果很好。

 builder.RegisterType<UpdateCustomerCommandHandler>().As<ICommandHandler<UpdateCustomerCommand>>();

当我说 "It doesn't work" 时,我的意思是在尝试实例化控制器时,我得到 "InvalidOperationException: Unable to resolve service for type 'BusinessLogic.ICommandHandler`1[BusinessLogic.UpdateCustomerCommand]' while attempting to activate 'AutoFac_Test.Controllers.ValuesController'."

这在该项目的 Full WebAPI 版本中运行良好,但在 ASP.NET Core 中重新创建后就不行了。需要明确的是,这在移植到 ASP.NET 核心之前运行良好。

这是我用来重现此问题的代码的 link: https://dl.dropboxusercontent.com/u/185950/AutoFac_Test.zip

**** 发现解决方案后编辑 ****

我的 Autofac 配置实际上没有任何问题,当然 Autofac 本身也没有。发生的事情是我重命名了我的依赖程序集的输出以使程序集扫描的东西(替换 AppDomain.CurrentDomain.GetAssemblies() 更优雅,但是我从未修改 API 项目的依赖项以引用新程序集。所以 Autofac 正在扫描正确加载的程序集,这些程序集恰好是旧版本,不包含我期望的接口和实现...

Autofac 内置支持注册封闭类型的开放泛型。

builder
    .RegisterAssemblyTypes(ThisAssembly)
    .AsClosedTypesOf(typeof(ICommandHandler<>));

这将扫描您的程序集,找到关闭开放通用 ICommandHandler<> 接口的类型,并针对它们实现的封闭通用接口注册它们中的每一个 - 在您的例子中,ICommandHandler<UpdateCustomerCommand>.

在您的示例中不起作用的是您将密钥关联到您的服务。尝试实例化 ValuesController 时,Autofac 不会查找 ICommandHandler<UpdateCustomerCommand> 的键控版本,这就是您得到异常的原因。

在 QuietSeditionist 的评论后编辑:

我将尝试详细说明 keyeddefault 服务。您注册处理程序的方式是将 commandHandler 键关联到它们。

这意味着构建容器后,这是解析此类处理程序的唯一方法:

// container will look for a registration for ICommandHandler<UpdateCustomerCommand> associated with the "commandHandler" key
container.ResolveKeyed<ICommandHandler<UpdateCustomerCommand>>("commandHandler");

实例化 ValuesController 时,Autofac 不会查找 ICommandHandler<UpdateCustomerCommand>keyed 注册,因为它没有被要求这样做。

它正在执行的等效代码是 - 您可以尝试 运行 自己编写该代码以获取异常:

// BOOM!
container.Resolve<ICommandHandler<UpdateCustomerCommand>>();

您第二次注册成功的原因是您没有密钥服务:

// No key
builder
    .RegisterType<UpdateCustomerCommandHandler>()
    .As<ICommandHandler<UpdateCustomerCommand>>();

// commandHandler key
builder
    .RegisterType<UpdateCustomerCommandHandler>()
    .Keyed<ICommandHandler<UpdateCustomerCommand>>("commandHandler");

但是由于您不想一一注册所有的处理程序,下面是如何在没有 keying 的情况下注册它们:

builder
    .RegisterAssemblyTypes(ThisAssembly)
    .AsClosedTypesOf(typeof(ICommandHandler<>));

/编辑

我可以看到 keying 服务有用的两个场景:

  • 您有多种实现相同接口的类型,并且您希望在不同的服务中注入不同的实现。比方说,您将 SqlConnectionDB2Connection 都注册为 IDbConnection。然后您有 2 个服务,一个应该以 SQL 服务器为目标,另一个是 DB2。如果它们都依赖于 IDbConnection,您要确保在每项服务中注入正确的服务。

  • 如果您使用 decorators,注册的工作方式是您定义装饰器将通过 key 应用的服务 - 第一个示例不言自明

因为即使您尝试手动注册类型,Google 也会将您带到此页面,我认为即使这不能回答所提出的问题,它对未来的访问者还是有用的。所以,如果你想手动注册一个泛型类型,你可以使用这种格式:

service.AddTransient(typeof(IThing<>), typeof(GenericThing<>));

或者如果没有界面,则只需:

service.AddTransient(typeof(GenericThing<>));

为了完整起见,如果您有一个具有多种类型的泛型:

services.AddTransient(typeof(GenericThing<,>));