本文介绍了Autofac - 无需传递容器即可解析运行时参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a simpler "ServiceHelper" class that takes two parameters in the constructor:

public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)

(Autofac 提供的 NLog 的 ILogger 通用包装器就好了,serviceName 是我需要在运行时提供的 Windows 服务的名称来控制.)

(ILogger generic wrapper for NLog that Autofac is providing just fine, and the serviceName is the name of a Windows service to control that I need to provide at runtime.)

我无法理解如何在运行时使用 Autofac 创建此类的新实例并传入不同的服务名称.这样的事情当然不起作用,因为我需要在运行时指定不同的服务名称:

I'm having trouble wrapping my head around how to create new instances of this class at runtime passing in different service names, using Autofac. Something like this doesn't work of course since I need to specify different service names at runtime:

builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();

从我读到的内容来看,传递容器并正确地手动调用 Resolve 是一个坏习惯(AutoFac 警告的服务定位器反模式"),还是这样?如果我这样做了,那么我可以做到

From what I've read, its a bad habit to pass the container around and call Resolve manually right (the Service Locator "anti-pattern" the AutoFac warns about), or is it? If I did that then I could do

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

但即便如此,我也不太确定如何让 Autofac 将容器注入到类中,它只需要像这样准确地注册自己?然后让我的类在它们的构造函数中需要一个 IContainer 吗?(这是在使用构造函数注入的 C# 服务中)

But to even get that far I'm not quite sure how to get Autofac to inject the container into the classes, it would just need to register itself how exactly, like this? And then have my classes require an IContainer in their constructors? (This is in a C# Service using constructor injection)

builder.RegisterType<Container>().As<IContainer>().InstancePerDependency();

我也读过关于委托工厂的内容,但这似乎并不能避免必须传递容器.

I read about delegate factories too but that doesn't seem to get away from having to pass the container around.

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

Really most of my classes that consume the ServiceHelper, just need 1 or 2 ServiceHelpers for specific service names, so its not like I'm making thousands with unexpected serviceName parameters, this is just making my head hurt a little.

推荐答案

是的,到处传递容器是一种反模式.

Yes, passing the container around everywhere is an anti-pattern.

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

You can avoid it by using a factory like this:

(注意:此答案中的所有代码都未经测试,我是在没有 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,就像其他任何事情一样:

On startup, you register the ServiceHelperFactory in Autofac, like everything else:

builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();

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

Then, when you need a ServiceHelper somewhere else, you can get the factory via constructor injection:

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 通过构造函数注入创建工厂本身,您可以确保工厂知道容器,而不必自己传递容器.

By creating the factory itself via constructor injection with Autofac, you make sure that the factory knows about the container, without having to pass the container around by yourself.

我承认,乍一看,这个解决方案与直接传递容器并没有太大区别.但好处是你的应用仍然与容器解耦——唯一知道容器的地方(除了启动)是在工厂内部.

I admit, at first glance this solution doesn't look very different than passing the container around directly. But the advantage is that your app is still decoupled from the container - the only place where the container is known (except startup) is inside the factory.

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

OK, I forgot. As I said above, I'm writing this on a machine without Visual Studio, so I'm not able to test my example code.
Now that I read your comment, I remember that I had a similar problem when I used Autofac and tried to register the container itself.

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

My problem was that I needed to register the container in the builder.
But to get the container instance to register, I needed to call builder.Build()...which creates the container, which means that I can't register stuff in the builder afterwards.
I don't remember the error message that I got, but I guess you have the same problem now.

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

The solution that I found was to create a second builder, register the container there, and then use the second builder to update the one and only container.

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

Here is my working code from one of my open source projects:

启动时,我注册了容器::

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);

...然后使用 通过 WindowService 创建新的 WPF 窗口:

...which is then used 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>();
    }
}

这篇关于Autofac - 无需传递容器即可解析运行时参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 10:46