C# Autofac—解析运行时参数而不必传递容器

C# Autofac—解析运行时参数而不必传递容器,c#,autofac,C#,Autofac,我有一个更简单的“ServiceHelper”类,它在构造函数中接受两个参数: public ServiceHelper(ILogger<ServiceHelper> log, string serviceName) publicservicehelper(ILogger日志,字符串serviceName) (Autofac提供的NLog的ILogger通用包装器非常好,serviceName是我需要在运行时提供的用于控制的Windows服务的名称。) 我很难理解如何在运行时使用

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

public ServiceHelper(ILogger<ServiceHelper> log, string serviceName)
publicservicehelper(ILogger日志,字符串serviceName)
(Autofac提供的NLog的ILogger通用包装器非常好,serviceName是我需要在运行时提供的用于控制的Windows服务的名称。)

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

builder.RegisterType<ServiceHelper>().As<IServiceHelper>().WithParameter(new NamedParameter("serviceName", null)).InstancePerDependency();
builder.RegisterType().As().WithParameter(新名称参数(“serviceName”,null)).InstancePerDependence();
据我所知,传递容器并手动调用Resolve是一个坏习惯(AutoFac警告的服务定位器“反模式”),或者是?如果我那样做了,我就能做到

container.Resolve<ServiceHelper>(new NamedParameter("serviceName", "some service name"));
container.Resolve(新名称参数(“serviceName”、“某些服务名称”);
但要达到这一步,我不太确定如何让Autofac将容器注入类中,它只需要准确地注册自己,就像这样?然后我的类是否需要在其构造函数中使用IContainer?(这是在使用构造函数注入的C#服务中)

builder.RegisterType().As().InstancePerDependence();
我也读过关于代理工厂的文章,但这似乎离不开传递容器


实际上,我的大多数使用ServiceHelper的类,只需要1或2个ServiceHelper来指定服务名称,所以这并不是说我用意外的serviceName参数创造了数千个,这只是让我有点头疼。

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

您可以使用如下工厂来避免:

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

然后,当您在其他地方需要一个
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()
…来创建容器,这意味着我以后不能在生成器中注册内容。
我不记得我收到的错误消息,但我想你现在也有同样的问题

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

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

:


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


要在设计时定义依赖项链,请参见主题…

我采用了上述方法,效果很好,但是我发现不可能进行单元测试,因为IContainer中的“Resolve”方法是一种扩展方法。它也从来没有真正感觉“正确”的所有谈论不传递您的容器

我回到绘图板,找到了使用Autofac委托工厂实例化对象的“正确”方法

因此工厂采用了IContainer,但我遇到了这个错误:使用构造函数查找器“Autofac.Core.Activators.Reflection.DefaultConstructorFinder”找不到类型为“Autofac.Core.Container”的构造函数。“}Autofac需要自己注册,对吗?无论哪种方式,我都会遇到类似的错误,但我正在尝试:builder.RegisterType().As().InstancePerLifetimeScope();我猜是IComponentContext而不是IContainer?试一试:我不知道
IComponentContext
,但我知道有关向自身注册Autofac的问题。我刚刚更新了我的答案!通常不需要引用IContainer。“询问”Autofac以获取ILifetimeScope。ILifetimeScope允许您解析组件、创建嵌套的生存期作用域并注册其他类型,这些类型仅在作用域内可用。容器是作用域后面的工厂,但当组件必须通过友好方式解析组件时,作用域是访问组件中Autofac的正确方式。要补充@a.Chiesa的观点,
ContainerBuilder.Update
现在不推荐使用。解决
ILifetimeScope
而不是
IContainer
是一条可行之路。
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));
    }
}
builder.RegisterType<ServiceHelperFactory>().As<IServiceHelperFactory>();
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");
    }
}
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);
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>();
    }
}