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

时间:2014-03-07 20:07:56

标签: c# autofac

我有一个更简单的“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参数制作了数千个,这只会让我头疼一点。

3 个答案:

答案 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的机器上写这个,所以我无法测试我的示例代码。
现在我读了你的评论,我记得当我使用Autofac并尝试注册容器本身时,我遇到了类似的问题。

我的问题是我需要在构建器中注册容器 但是要让容器实例注册,我需要调用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 ...