我有一个更简单的“ServiceHelper”类,它在构造函数中包含两个参数:
public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)
(用于NOG的ILogger通用包装器,Autofac提供的很好,而serviceName是用于控制我需要在运行时提供的Windows服务的名称。)
我无法使用Autofac在运行时以不同的服务名称创建此类的新实例。这样的事情当然不起作用,因为我需要在运行时指定不同的服务名称:
builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();
从我读过的内容来看,一个坏习惯是通过容器并手动调用Resolve(AutoFac警告的服务定位器“反模式”),或者是它?如果我这样做那么我就可以做到
container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));
但是即使走得那么远,我也不太确定如何让Autofac将容器注入到类中,它只需要注册自己的确切方式,就像这样?然后让我的类在他们的构造函数中需要一个IContainer? (这是使用构造函数注入的C#服务)
builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();
我也读到了委托工厂,但似乎并没有摆脱必须通过容器。
我的大多数使用ServiceHelper的类只需要1或2个ServiceHelper来获取特定的服务名称,所以它不像我使用意外的serviceName参数制作了数千个,这只会让我头疼一点。
答案 0 :(得分:21)
是的,将容器放在任何地方都是一种反模式。
您可以使用这样的工厂来避免它:
(注意:此答案中的所有代码均未经过测试,我在没有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<ServiceHelper>(new NamedParameter("serviceName", serviceName));
}
}
启动时,您在Autofac中注册ServiceHelperFactory
,就像其他所有内容一样:
builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();
然后,当你在其他地方需要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的机器上写这个,所以我无法测试我的示例代码。我的问题是我需要在构建器中注册容器
但是要让容器实例注册,我需要调用builder.Build()
...创建容器,这意味着我之后无法在构建器中注册东西。
我不记得我得到的错误信息,但我猜你现在也有同样的问题。
我找到的解决方案是创建第二个构建器,在那里注册容器,然后使用第二个构建器更新唯一的容器。
以下是我的一个开源项目的工作代码:
On startup, I register the container::
var builder = new ContainerBuilder();
// register stuff here
var container = builder.Build();
// register the container
var builder2 = new ContainerBuilder();
builder2.RegisterInstance<IContainer>(container);
builder2.Update(container);
...然后使用by a WindowService
to create new WPF windows:
public class WindowService : IWindowService
{
private readonly IContainer container;
public WindowService(IContainer container)
{
this.container = container;
}
public T GetWindow<T>() where T : MetroWindow
{
return (T)this.container.Resolve<T>();
}
}
答案 1 :(得分:7)
我走上了上述方法的路径并且工作正常,但我发现由于&#34; Resolve&lt;&gt;&#34;&#34; Resolve&lt;&gt;&#34; IContainer中的方法是一种扩展方法。它也从来没有真正感受过#34;对&#34;所有谈论都没有通过你的容器。
我回到绘图板,发现&#34;正确&#34;使用Autofac Delegate Factories实例化对象的方法 http://docs.autofac.org/en/latest/advanced/delegate-factories.html
答案 2 :(得分:3)
仅对根组合对象进行解析。调用解决方案几乎与“新建”对象相同,这是一种气味测试。有时候分辨率是动态的,只能在运行中确定,但大多数依赖关系是确定性的,可以预先注册。使用Autofac如何做到这一点是一项挑战。 @Christian Specht的答案是一个很好的答案,但它假设一切都在运行时确定。
要在设计时定义依赖关系链,请参阅SO主题Autofac sub-dependencies chain registration ...