【问题标题】:How to access Ninject.Kernel without using Service Locator pattern如何在不使用服务定位器模式的情况下访问 Ninject.Kernel
【发布时间】:2012-08-08 22:07:40
【问题描述】:

我已经阅读了数十篇关于这个主题的帖子,但没有找到关于如何在不使用服务定位器模式的情况下访问 Ninject.Kernel 的明确指南。

我目前在需要使用CustomerBusiness(这是我的服务)的课程中有以下内容,它工作正常,但我很清楚这不是推荐的做法。

private CustomerBusiness _customerBusiness;

private ICustomerRepository CustomerRepository
{
    get { return NinjectWebCommon.Kernel.Get<IAccountRepository>(); }
}

private CustomerBusiness CustomerBusiness
{
    get
    {
        if (_customerBusiness == null)
        {
            _customerBusiness = new CustomerBusiness(AccountRepository);
        }

        return _customerBusiness;
    }
}

public Customer GetCustomer(int id)
{
    return CustomerBusiness.GetCustomer(id);
}

这是上面代码中访问的 Kernel 属性:

public static IKernel Kernel
{
    get
    {
        return CreateKernel();
    }
}

我已经阅读了很多关于为此使用工厂的建议,但没有一个解释如何使用这个工厂。如果有人可以向我展示“CustomerFactory”或任何其他推荐的方法包括如何使用,我将不胜感激。

更新

我正在使用 ASP.NET Web 窗体,需要从 CodeBehind 访问 CustomerBusiness

解决方案

我发现有效的最终解决方案是在这篇文章中找到的得票最多的答案: How can I implement Ninject or DI on asp.net Web Forms?

看起来像这样(注意从 PageBase 继承,它是 Ninject.Web 的一部分 - 这是关键!):

public partial class Edit : PageBase
{
    [Inject]
    public ICustomerBusiness CustomerBusiness { get; set; }
    ...

下面接受的答案间接引导我找到这个解决方案。

【问题讨论】:

    标签: c# dependency-injection ninject


    【解决方案1】:

    由于您使用的是NinjectWebCommon,我假设您有某种网络应用程序。你真的应该只在一个地方访问 Ninject 内核 - 在composition root。它是您构建对象图的地方,也是您唯一需要访问 IoC 容器的地方。要真正获得所需的依赖项,您通常使用constructor injection

    例如,对于 MVC Web 应用程序,您有一个使用 Ninject 内核的控制器工厂,这是唯一引用它的地方。

    为了扩展您的特定情况,您的类将在其构造函数中接受 ICustomerBusiness,并声明它需要一个 ICustomerBusiness 的实例作为其依赖项:

    class CustomerBusinessConsumer : ICustomerBusinessConsumer
    {
        private readonly ICustomerBusiness customerBusiness;
    
        public CustomerBusinessConsumer(ICustomerBusiness customerBusiness)
        {
            this.customerBusiness = customerBusiness;
        }
        ...
    }
    

    现在,无论哪个类使用ICustomerBusinessConsumer 作为其依赖项,都将遵循相同的模式(接受ICustomerBusinessConsumer 的实例作为其构造函数参数)。你基本上永远不要手动构建你的依赖项使用new(除了特定的例外)。

    然后,您只需要确保您的类获得它们的依赖项,并且您在此组合根中执行此操作。组合根到底是什么取决于您正在编写的应用程序的类型(控制台应用程序、WPF 应用程序、Web 服务、MVC Web 应用程序......)


    编辑:让自己熟悉 ASP.NET WebForms 领域的情况 由于我从未使用过它,因此我不得不查看详细信息。不幸的是,WebForms 要求您在每个 Page 类中都有一个无参数构造函数,因此您不能从对象图的顶部一直到底部使用构造函数注入。

    但是,在查阅Mark Seeman 的关于在 WebForms 中组合对象的章节之后,我可以重新表述如何处理这个框架的低效率,同时仍然符合良好的 DI 实践:

    1. 有一个类负责解决依赖关系,使用您设置的 Ninject 内核。这可能是内核周围的一个非常薄的包装器。我们就叫它DependencyContainer吧。

    2. 创建您的容器并将其保存在应用程序上下文中,以便在您需要时准备好它

      protected void Application_Start(object sender, EventArgs e)
      {
         this.Application["container"] = new DependencyContainer();
      }
      
    3. 假设您的页面类(我们称之为HomePage)依赖于ICustomerBusinessConsumer。然后DependencyContainer 必须允许我们检索ICustomerBusinessConsumer 的实例:

      public ICustomerBusinessConsumer ResolveCustomerBusinessConsumer()
      {
          return Kernel.Get<ICustomerBusinessConsumer>();
      }
      
    4. 比在MainPage 类本身中,您将在默认构造函数中解析其依赖关系:

      public MainPage()
      {
          var container = (DependencyContainer) HttpContext.Current.Application["container"];
          this.customerBusinessConsumer = container.ResolveCustomerBusinessConsumer();
      }
      

    几点说明:

    • HttpContext 中拥有可用的依赖容器一定不要将其视为服务定位器。事实上,这里的最佳实践(至少从忠实于 DI 的角度来看)是拥有某种“实现器”类,您将向其传递页面类的功能。

      例如,MainPage 处理的每个操作将仅中继到其实现者类。这个实现类将是 MainPage 的依赖项,并且与所有其他依赖项一样,将使用容器解析。

      重要的是这些实现类应该在不引用 ASP.NET 程序集的程序集中,因此没有机会访问 HttpContext

    • 不得不编写这么多代码来实现这一点当然不理想,但这只是框架限制的结果。例如,在 ASP.NET MVC 应用程序中,这种情况的处理方式要好得多。在那里,您可以单点组成对象图,而不必像在 WebForms 中那样在每个顶级类中解析它们。

    • 好消息是,虽然您必须在页面类构造函数中编写一些管道代码,但您可以从那里向下对象图使用构造函数注入

    【讨论】:

    • 但是看看上面的类。我假设您会建议我让我的构造函数采用 ICustomerBusiness?我从哪里构造它,所以构造函数注入生效。我必须从某个地方开始,对吧?
    • @NielsBrinch twoflower 实际上已经回答了这个问题,“你真的应该只在一个地方访问 Ninject 内核 - 在组合根目录。”
    • @NielsBrinch 我扩展了我的答案,希望对您有所帮助。如果你具体说明你有什么应用程序,我还可以写更多关于你的情况下的组合根。
    • @Niels:组合根意味着你的内核在一个类中,从那里可供其他所有人使用。实际上,这意味着您要么拥有一个共享类并公开内核,要么隐藏内核并公开解决依赖关系(创建对象)的方法。
    • 太好了,我们正在取得进展!让我们关注使用 CustomerBusinessConsumer 的类。如果我正确理解 webapps 的组合根推荐,这将不得不以某种方式访问​​内核,也许通过工厂。这不会导致与我最初发布的代码或多或少相同吗?
    【解决方案2】:

    构造函数注入是带有 ninject 的 DI 的首选方法,但它也支持属性注入。在此处阅读有关注入模式的 ninject 页面https://github.com/ninject/ninject/wiki/Injection-Patterns

    这两种都是注入模式,不像服务位置是基于请求的,根本不是真正的注入。

    注入的另一面是您需要控制构造。使用 MVC 时,执行此操作的所有接线都内置在 nuget 上的 MVC ninject 包中

    【讨论】:

    • 我在其他任何地方都使用构造函数注入,例如,存储库位于业务类的构造函数中,效果很好。但我必须从某个地方开始,对吧?我不能在只需要使用类的类中使用构造函数注入。
    • @NielsBrinch 通常在您的应用程序中只有一个入口点,在 webapps 中这围绕 global.ascx,在控制台应用程序中围绕 Program.Main,通常 ninject 配置在此级别以及以下任何内容这是用ninject构造的。如果你在一个 webapp 中,有一些包会为你设置这个,以便从内核实例化 onrequest 事物。 WPF 和 silverlight 的情况在较小程度上也是如此。如果您在其他应用程序类型中,您可能必须从内核手动构造您的祖先对象
    • 我在一个网络应用程序中。我在相当于 global.asax (App_Start/NinjectWebCommon) 中构建内核。你是否建议我在这个类中也创建 CustomerBusiness 等并将它们放在某个地方以供以后使用?
    • @NielsBrinch 如果您在 web 应用程序中,您实际上只需要收到请求后的对象。此时 ninject 将实例化或重用您的 CustomerBusiness 以在该请求中使用
    • 这是什么样的?想想没有构造函数并且需要使用 CustomerBusiness 的代码隐藏。
    猜你喜欢
    • 2011-02-24
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    • 2014-12-10
    • 2018-02-01
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多