使用ASP.NET Core中的Autofac注册泛型类型的问题

时间:2016-12-13 22:48:10

标签: dependency-injection asp.net-core autofac

我是Autofac和ASP.NET Core的新用户。我最近将一个小项目从“经典”ASP.NET WebAPI项目移植到ASP.NET Core。我在使用Autofac时遇到了麻烦,特别是在通用类型的注册方面。

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

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>>();

当我说“它不起作用”时,我的意思是当尝试实例化控制器时,我得到“InvalidOperationException:无法解析类型'BusinessLogic.ICommandHandler`1 [BusinessLogic.UpdateCustomerCommand]'的服务试图激活'AutoFac_Test.Controllers.ValuesController'。“

这在此项目的完整WebAPI版本中运行良好,但在ASP.NET Core中重新创建后却无效。需要说明的是,在移植到ASP.NET Core之前,这种方法非常有效。

以下是我用来重新创建此问题的代码的链接: https://dl.dropboxusercontent.com/u/185950/AutoFac_Test.zip

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

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

2 个答案:

答案 0 :(得分:0)

Autofac内置支持注册封闭类型的开放式通用。

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

这将扫描您的程序集,找到关闭开放式通用ICommandHandler<>接口的类型,并根据它们实现的封闭通用接口注册每个类型 - 在您的情况下,ICommandHandler<UpdateCustomerCommand>

您的示例中没有用的是您将密钥与服务相关联。在尝试实例化ICommandHandler<UpdateCustomerCommand>时,Autofac不会查找ValuesController的键控版本,这就是您获得例外的原因。

在QuietSeditionist评论后编辑:

我将尝试详细说明键控默认服务。注册处理程序的方法是将commandHandler键与它们相关联。

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

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

在实例化ValuesController时,Autofac不会查找ICommandHandler<UpdateCustomerCommand>键入注册,因为它没有被要求。

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

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

您的第二次注册有效的原因是您没有该服务:

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

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

但是,既然您不想逐个注册所有处理程序,那么如何在不键入的情况下注册它们:

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

/编辑

我可以看到键控服务有用的两种情况:

  • 您有几种类型实现相同的接口,并且您希望在不同的服务中注入不同的实现。我们说,您将SqlConnectionDB2Connection注册为IDbConnection。然后,您有2个服务,一个应该针对SQL Server,另一个针对DB2。如果他们都依赖IDbConnection,您需要确保在每项服务中注入正确的一个。

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

答案 1 :(得分:0)

因为即使您尝试手动注册类型,Google也会将您带到此页面,但我认为即使这并不能回答问题,这对未来的访问者也很有用。因此,如果要手动注册泛型类型,可以使用以下格式:

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

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

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

并且为了完整性,如果您有多种类型的泛型:

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