【问题标题】:How to pass parameters into constructors when using IoC containers?使用 IoC 容器时如何将参数传递给构造函数?
【发布时间】:2015-09-29 09:38:32
【问题描述】:

啊!我在这里拉头发。我一直在尝试使用 IoC 容器,在遇到一些你认为非常基本的问题之前,一切看起来都很好,很花哨,比如将参数传递给构造函数。

假设我在某个地方有一个类,其中混合了可以由 IoC 解析的引用类和只能在运行时解析的值类型(或其他一些类型):

public NFLFeedUnitOfWork(NFLFileType fileType, object feed, IConverterMappings<NFLFileType> nflConverterMappings, IDbContext context)
    : base(fileType, feed, nflConverterMappings, context, ContextType.NFL)
{
    //new NFLContext(connstringname, setAutoDetectChanges)
}

在这个特定的示例中,我传入 Enum (NFLFileType)、对象实例、2 个接口参数并将一个额外的硬编码属性传入基本构造函数 (ContextType.NFL)

以众神的名义,我怎么能在任何 IoC 容器中做到这一点?

问题实际上是2-fold:

1.) 如何传入仅在运行时才知道的对象?比如说现在调用代码是这样的:

protected override IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed, string connectionString)
{
    return new NFLFeedUnitOfWork(fileType, feed, new NFLConverterMappings(), new NFLContext(connectionString));
}

如何将此代码转换为使用 IoC? 也许是这样的?

protected override IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed, string connectionString)
{
    return IFLFeedUnitOfWork(fileType, feed);
}

最后2个参数在哪里自动解析,第一个2个我自己提供?

2.) 如何使用 IoC 将枚举、对象、值类型传递给构造函数? (或者也许在这个特定的情况下不要使用它?)

无论如何,非常感谢任何帮助,尤其是在第一点。 我目前正在使用 Unity,但任何其他 IoC 容器也可以。

我也不想将 IoC 容器传递到代码中,我只想在顶层的一个地方指定它。

【问题讨论】:

    标签: .net ninject unity-container ioc-container


    【解决方案1】:

    您有几个不同的选项,具体取决于值的来源。 (我只熟悉 Ninject,我认为 Unity 也有类似的功能)

    如果数据依赖于其他服务或存储库,您可以将对象绑定到委托,以便在满足请求时解析它。例如:

    Bind<NFLFileType>().ToMethod( context => context.Kernel.Get<IConfigProvider>().NFLFileType );
    

    Ninject 还支持仅在某些情况下使用.When() 语法解析的绑定。例如:

    Bind<NFLFileType>().ToConstant( NFLFileType.MyValue ).WhenInjectedInto<NFLFeedUnitOfWork>();
    
    Bind<NFLFileType>().ToConstant( NFLFileType.MyValue ).When(request => request.Target.Type.GetCustomAttributes(typeof(MyValueAttribute)) != null );
    

    如果一切都失败了,您可以将注入的接口绑定到一个像工厂模式一样工作的 Provider 以生成对象:

    Bind<IFeedUnitOfWork>().ToProvider<UnitOfWorkProvider>();
    

    并且 Provider 知道如何根据上下文解析前 2 个参数。

    【讨论】:

      【解决方案2】:

      我在某处有一个类,其中包含多个参考类,可以 由 IoC 和值类型(或其他一些类型)解决 在运行时解决

      这就是你出错的地方。编写组件时,不应将编译时依赖项与运行时数据混为一谈。您的对象图应该是静态的(最好是无状态的),并且运行时数据应该在构建完整的对象图之后使用方法调用通过对象图传递。这可以极大地简化您的应用程序的开发,因为它允许您的对象图被静态验证(使用工具、单元测试或使用纯 DI),并且它可以避免您今天遇到的麻烦。

      一般来说,您有两种选择来解决这个问题:

      1. 您可以通过方法调用将数据传递给较低级别​​的组件。
      2. 您可以通过调用注入组件的方法来检索数据。

      采取哪种解决方案取决于具体情况。

      如果数据特定于所处理的请求并且是您正在处理的用例的一部分,您通常会选择选项一。例如:

      public interface IFeedUnitOfWorkProvider
      {
          IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed);
      }
      

      这里的IFeedUnitOfWorkProvider 包含一个需要运行时参数作为输入的GetUnitOfWork 方法。实现可能如下所示:

      public class FeedUnitOfWorkProvider : IFeedUnitOfWorkProvider
      {
          private readonly IConverterMappings converterMappings;
          private readonly IContext context;
      
          public FeedUnitOfWorkProvider(IConverterMappings converterMappings,
              IContext context) {
              this.converterMappings = converterMappings;
              this.context = context;
          }
      
          public IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed) {
              return new NFLFeedUnitOfWork(fileType, feed, this.converterMappings,
                  this.context);
          }       
      }
      

      这里有几点需要注意:

      • 所有静态已知的依赖项都通过构造函数注入,而运行时值则通过方法调用注入。
      • connectionString 的值对于 FeedUnitOfWorkProvider 是未知的。它不是直接依赖,而且提供者不必知道它的存在。
      • 假设connectionString是一个在运行时不会改变的配置值(并且通常存储在应用程序的配置文件中),它可以像注入其他依赖项一样注入NFLContext。请注意,配置值与运行时值不同,因为它在应用程序的生命周期内不会改变。

      当您处理上下文信息时,第二个选项特别有用。这是对实现很重要的信息,但不应像前面的示例一样传入。一个很好的例子是关于代表谁运行请求的用户的信息。对此的典型抽象如下所示:

      public interface IUserContext {
          string CurrentUserName { get; }
      }
      

      这个接口可以注入任何消费者。使用这种抽象,消费者可以在运行时查询用户名。将用户名与其余的请求数据一起传递通常会很尴尬,因为这将允许调用者更改(或忘记)用户名,从而使代码更难使用、更难测试、更容易出错。相反,我们可以使用IUserContext:

      public IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed) {
          if (this.userContext.CurrentUserName == "steven") {
              return new AdminUnitOfWork(this.context);
          }
          return new NFLFeedUnitOfWork(fileType, feed, this.converterMappings,
              this.context);
      }
      

      IUserContext 实现的外观在很大程度上取决于您正在构建的应用程序的类型。对于 ASP.NET,我想像这样:

      public class AspNetUserContext : IUserContext {
          string CurrentUserName {
              get { return HttpContext.Current.User.Name; }
          }
      }
      

      【讨论】:

      • +1。这显示了 OP 痛点的原因并提供了很好的建议。 Unity 还能够提供 ResolverOverrides,允许您在解析时覆盖现有注册(如Resolving Objects by Using Overrides)。我不愿提及这一点,因为我认为上面的建议应该是第一种方法。
      • 这么好的答案!!!非常感谢史蒂文,这绝对回答了我的大部分问题,并为我指明了正确的方向。但是,您能澄清一下吗?在为案例 1 提供的示例中,我们基本上为调用 GetUnitOfWork 方法的类创建了一个 Provider。但是在GetUnitOfWork 本身的实现中,我们仍然将2 个仅在运行时知道的值传递给new NFLFeedUnitOfWork(...,这是否意味着我还需要为NFLFileType fileTypeobject feed 创建2 个其他提供程序? (这似乎有点太多了,1 个提供者为 1 个运行时值)
      • @Tanuki:我无法回答这个问题。我不知道那些 vakues 是什么,以及它们是如何确定的。请使用有关此信息的信息更新您的问题,我会尝试查看。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-28
      相关资源
      最近更新 更多