我是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正在扫描正确加载的程序集,这些程序集恰好是旧版本,它不包含我期望的接口和实现......
答案 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<>));
/编辑
我可以看到键控服务有用的两种情况:
您有几种类型实现相同的接口,并且您希望在不同的服务中注入不同的实现。我们说,您将SqlConnection
和DB2Connection
注册为IDbConnection
。然后,您有2个服务,一个应该针对SQL Server,另一个针对DB2。如果他们都依赖IDbConnection
,您需要确保在每项服务中注入正确的一个。
如果您使用decorators,注册的工作方式是您通过键定义装饰器将应用的服务 - 第一个示例是不言自明的
答案 1 :(得分:0)
因为即使您尝试手动注册类型,Google也会将您带到此页面,但我认为即使这并不能回答问题,这对未来的访问者也很有用。因此,如果要手动注册泛型类型,可以使用以下格式:
service.AddTransient(typeof(IThing<>), typeof(GenericThing<>));
或者如果没有界面,那么只需:
service.AddTransient(typeof(GenericThing<>));
并且为了完整性,如果您有多种类型的泛型:
services.AddTransient(typeof(GenericThing<,>));