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

Jer*_*emy 24 c# autofac

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

public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)
Run Code Online (Sandbox Code Playgroud)

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

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

builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();
Run Code Online (Sandbox Code Playgroud)

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

container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));
Run Code Online (Sandbox Code Playgroud)

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

builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();
Run Code Online (Sandbox Code Playgroud)

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

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

Chr*_*cht 23

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

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

(注意:这个答案中的所有代码都是未经测试的,我是在没有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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();
Run Code Online (Sandbox Code Playgroud)

然后,当您需要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");
    }
}
Run Code Online (Sandbox Code Playgroud)

通过使用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<IContainer>(container);
builder2.Update(container);
Run Code Online (Sandbox Code Playgroud)

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

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 为了补充@A.Chiesa 的观点,现在不推荐使用`ContainerBuilder.Update`。解决“ILifetimeScope”而不是“IContainer”是要走的路。http://stackoverflow.com/questions/4609360/resolve-icontainer (4认同)
  • 通常您不需要引用IContainer."询问"Autofac的ILifetimeScope.ILifetimeScope允许您解析组件,创建嵌套的生命周期范围以及注册其他类型,仅在范围内可用.Container是作用域后面的工厂,但是范围是在组件中访问Autofac的正确方法,当他们必须以动态方式解析组件时. (3认同)

rat*_*rus 7

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

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