【问题标题】:Autofac - resolving runtime parameters without having to pass container aroundAutofac - 无需传递容器即可解析运行时参数
【发布时间】:2014-03-07 20:07:56
【问题描述】:

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

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

(Autofac 提供的用于 NLog 的 ILogger 通用包装器很好,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 参数赚了几千个,这只是让我有点头疼。

【问题讨论】:

    标签: c# autofac


    【解决方案1】:

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

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

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

    【讨论】:

    • 所以工厂采用 IContainer - 但是我收到此错误:使用构造函数查找器“Autofac.Core.Activators”找不到类型为“Autofac.Core.Container”的构造函数.Reflection.DefaultConstructorFinder'."} Autofac 需要自己注册,对吗?无论哪种方式,我都会遇到类似的错误,但我正在尝试:builder.RegisterType().As ().InstancePerLifetimeScope();
    • 我猜是 IComponentContext 而不是 IContainer?试一试:stackoverflow.com/questions/4609360/resolve-icontainer
    • 我不知道IComponentContext,但我知道自己注册Autofac的问题。我刚刚更新了我的答案!
    • 通常不需要引用IContainer。 “向”Autofac 询问 ILifetimeScope。 ILifetimeScope 允许您解析组件、创建嵌套的生命周期范围以及注册仅在范围内可用的其他类型。 Container 是作用域后面的工厂,但是当组件必须动态解析组件时,作用域是访问组件中 Autofac 的正确方法。
    • 补充@A.Chiesa 的观点,ContainerBuilder.Update 现在已弃用。解决ILifetimeScope 而不是IContainer 是要走的路。 stackoverflow.com/questions/4609360/resolve-icontainer
    【解决方案2】:

    我采用了上述方法,它运行良好,但是我发现无法进行单元测试,因为 IContainer 中的“Resolve”方法是一种扩展方法。所有关于不传递容器的讨论也从来没有真正感觉“正确”。

    我回到绘图板上,找到了使用 Autofac 委托工厂实例化对象的“正确”方法 http://docs.autofac.org/en/latest/advanced/delegate-factories.html

    【讨论】:

      【解决方案3】:

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

      要在设计时定义依赖链,请参阅 SO 主题 Autofac sub-dependencies chain registration...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多