Autofac – 无需传递容器即可解析运行时参数

我有一个更简单的“ServiceHelper”类,它在构造函数中接受两个参数:

public ServiceHelper(ILogger log, string serviceName) 

(用于NOG的ILogger通用包装器,Autofac提供的很好,而serviceName是控制我需要在运行时提供的Windows服务的名称。)

我在使用Autofac在运行时传递不同服务名称时如何创建此类的新实例时遇到了麻烦。 这样的东西当然不起作用,因为我需要在运行时指定不同的服务名称:

 builder.RegisterType().As().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency(); 

从我所读到的,它是一个坏习惯传递容器并手动调用Resolve(AutoFac警告的服务定位器“反模式”),或者是它? 如果我这样做那么我就可以做到

 container.Resolve(new NamedParameter("serviceName", "some service name")); 

但是,即使到目前为止,我也不太确定如何让Autofac将容器注入到类中,它只需要注册自己的确切方式,就像这样? 然后让我的类在他们的构造函数中需要一个IContainer? (这是使用构造函数注入的C#服务)

 builder.RegisterType().As().InstancePerDependency(); 

我也读到了委托工厂,但似乎并没有摆脱必须通过容器。

实际上我的大多数类使用ServiceHelper,只需要1或2个ServiceHelper来获取特定的服务名称,所以它不像我使用意外的serviceName参数制作数千个,这只是让我头疼一点。

是的,将容器绕过各地是一种反模式。

您可以通过使用这样的工厂来避免它:

(注意:这个答案中的所有代码都是未经测试的,我是在没有Visual Studio的机器上的文本编辑器中编写的)

 public interface IServiceHelperFactory { IServiceHelper CreateServiceHelper(string serviceName); } public class ServiceHelperFactory : IServiceHelperFactory { private IContainer container; public ServiceHelperFactory(IContainer container) { this.container = container; } public IServiceHelper CreateServiceHelper(string serviceName) { return container.Resolve(new NamedParameter("serviceName", serviceName)); } } 

在启动时,您在Autofac中注册ServiceHelperFactory ,就像其他所有内容一样:

 builder.RegisterType().As(); 

然后,当您在其他地方需要ServiceHelper ,可以通过构造函数注入来获取工厂:

 public class SomeClass : ISomeClass { private IServiceHelperFactory factory; public SomeClass(IServiceHelperFactory factory) { this.factory = factory; } public void ThisMethodCreatesTheServiceHelper() { var helper = this.factory.CreateServiceHelper("some service name"); } } 

通过使用Autofac的构造函数注入创建工厂本身,您可以确保工厂知道容器,而不必自己通过容器。

我承认,乍一看这个解决方案与直接传递容器看起来并没有什么不同。 但优点是你的应用程序仍然与容器分离 – 容器已知的唯一地方(启动除外)在工厂内。


编辑:

好的,我忘了。 正如我上面所说,我在没有Visual Studio的机器上写这个,所以我无法测试我的示例代码。
现在我读了你的评论,我记得当我使用Autofac并尝试注册容器本身时,我遇到了类似的问题。

我的问题是我需要在构建器中注册容器。
但是为了让容器实例注册,我需要调用builder.Build() …来创建容器,这意味着我之后无法在构建器中注册东西。
我不记得我得到的错误信息,但我猜你现在有同样的问题。

我找到的解决方案是创建第二个构建器,在那里注册容器, 然后使用第二个构建器来更新唯一的容器

这是我的一个开源项目的工作代码:

在启动时,我注册容器 :

 var builder = new ContainerBuilder(); // register stuff here var container = builder.Build(); // register the container var builder2 = new ContainerBuilder(); builder2.RegisterInstance(container); builder2.Update(container); 

…然后由WindowService用于创建新的WPF窗口 :

 public class WindowService : IWindowService { private readonly IContainer container; public WindowService(IContainer container) { this.container = container; } public T GetWindow() where T : MetroWindow { return (T)this.container.Resolve(); } } 

我走上了上述方法的路径并且工作正常,但是由于IContainer中的“Resolve <>”方法是一种扩展方法,我发现单元测试是不可能的。 所有谈论都没有通过你的容器,它也从来没有真正感觉“正确”。

我回到绘图板,找到了使用Autofac Delegate Factories实例化对象的“正确”方法http://docs.autofac.org/en/latest/advanced/delegate-factories.html

只应对根组合对象进行解析。 调用解决方案几乎与“新建”对象相同,这是一种气味测试。 有时候分辨率是动态的,并且只能在运行中确定,但大多数依赖关系是确定性的,可以预先注册。 使用Autofac如何做到这一点是一项挑战。 @Christian Specht的答案是一个很好的答案,但它假设一切都在运行时确定。

要在设计时定义依赖关系链,请参阅SO主题Autofac子依赖关系链注册 …