【问题标题】:What is the best way to pass runtime constructor parameters to Autofac?将运行时构造函数参数传递给 Autofac 的最佳方法是什么?
【发布时间】:2018-01-07 16:41:51
【问题描述】:

我正在构建一个通用的中间件软件(Windows 服务),它将执行许多jobs.. 例如:

  • 同步库存
  • 进口订单

我将 C# 与 Windows 服务一起使用,并使用 Quartz.NET 来安排作业运行。 我也在使用 AutoFac。

据我了解,AutoFac 依赖项应该构建在组合根目录中。这很好..但是,我有某些注入的服务需要运行时参数(来自数据库的配置值)。

例如:

  • 某些作业将连接到 SFTP 服务器,连接详细信息将作为键值对存储在数据库中
  • 一些作业将连接到远程 API,从而将这些 API 身份验证详细信息与作业存储在数据库中。

我对此进行了一些研究,一些替代方案基本上建议删除其中一些服务的构造函数(例如 SFTP 客户端)并将它们作为配置方法传递..

而不是客户端有一个构造函数,如

SftpClient(string host, string username, string password, int timeoutInSeconds)

它将有一个默认构造函数,以及一个您将它们传入的配置方法。

我一点也不喜欢这个——它违背了我所学到的,你应该尝试配置你的对象,使它通过构造函数处于一致的状态。

什么是最好的选择? 我的JobFactory 方法目前依赖于IComponentContext。 我已经看到有一些方法可以将参数传递给 AutoFac 以构造对象,但我读过的东西表明它并不理想。 使用我的工厂会更好吗

【问题讨论】:

    标签: c# autofac


    【解决方案1】:

    您可以利用delegate factories 在运行时传递自定义参数。在这种情况下,您不必创建任何自定义工厂,并且在您的测试中模拟也很容易。

    您不能使用内置的Func<X,Y,T> 委托,因为您必须传递的参数类型相同。但是你可以介绍你自己的代表。如果你有一个单独的SmtpClient 接口,它看起来像这样

    public delegate ISmtpClient SmtpClientFactory(string host, string username, string password, int timeout);
    

    那么,你应该注入SmptClientFactory,而不是注入ISmtpClient。无需额外注册。

    如果您没有ISmtpClient 接口,它看起来几乎相同,但以后会更难测试。

    【讨论】:

      【解决方案2】:

      最简单的解决方案是注入工厂而不是实例。不要注入 AutoFac 容器本身(隐藏依赖项)。只需编写一个简单的类。

      class SftpClientFactory : ISftpClientFactory
      {
          public SftpClient GetClient(string host, string userName, string password, int timeout)
          {
              return new SftpClient(host, userName, password, timeout);
          }
      }
      

      然后像这样注册:

      container.RegisterType<ISftpFactory, SftpFactory>();
      

      在你的课堂上

      class Example
      {
          private readonly ISftpClientFactory _clientFactory;
      
          public Example(ISftpClientFactory injectedFactory)
          {
              _clientFactory = injectedFactory;
          }
      
          public void DoTheWork()
          {
              var client = _clientFactory.GetClient(host, userName, password, timeout);
          }
      }
      

      作为奖励,您现在可以完全控制对象生命周期,这听起来对于 sftp 客户端可能很重要,它可能拥有非托管资源并且是 Disposable

          public void DoTheWork()
          {
              using (var client = _clientFactory.GetClient(host, userName, password, timeout))
              {
                  client.DownloadFile();
              } 
          }
      

      此方案保留了控制反转,您仍然保留单个组合根,客户端仍然可以被您的单元测试项目存根,并且您仍然可以获得依赖项的编译时解析。唯一的缺点是编写存根需要更多的工作,因为你也必须编写存根工厂。

      【讨论】:

      • 谢谢 - 我假设工厂类将与客户端本身在同一个命名空间中?例如,我有一个单独的“Common”项目,这些公共服务和客户所在的地方——他们会进入那个命名空间,而不是消费者的命名空间,对吧?
      • 正确。工厂与它创建的类一起使用。消费者不应该知道或关心实例来自哪里,只要它符合接口。
      • 当我有嵌套依赖项时,我看不到如何使用此方法。例如,ISmtpClient 依赖于 IDependancy,它也需要一些运行时值。这将如何工作?
      • 嗯。不知道。通常,当使用构造函数参数实例化一个类时,它不是将在 IoC 容器中注册的类型......注入的类倾向于将构造函数参数用于注入接口,而不是用于传递值。
      猜你喜欢
      • 1970-01-01
      • 2022-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-10
      • 1970-01-01
      相关资源
      最近更新 更多