问题 如何在不使用Service Locator模式的情况下访问Ninject.Kernel


我已经阅读了几十篇关于这个主题的帖子,没有找到如何在不使用服务定位器模式的情况下访问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);
}

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

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

我已经阅读了许多关于使用工厂的建议,但没有一个解释如何 使用 这个工厂。如果有人能向我展示“CustomerFactory”或任何其他推荐方法,我将非常感激 包含 如何 使用 它。

更新

我正在使用ASP.NET Web Forms并需要访问 CustomerBusiness 来自CodeBehind。

我找到的最终解决方案是在这篇文章中找到的票数最多的答案: 如何在asp.net Web窗体上实现Ninject或DI?

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

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

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


4445
2017-08-12 10:15


起源



答案:


因为你正在使用 NinjectWebCommon,我假设您有某种Web应用程序。你真的应该只在一个地方访问Ninject内核 - 在 组成根。它是您构建对象图的地方,也是您唯一需要访问IoC容器的地方。要实际获得所需的依赖项,通常需要使用 构造函数注入

例如,对于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类中都有一个无参数构造函数,因此您无法从对象图的顶部一直到底部使用构造函数注入。

但经过咨询后 马克西曼关于在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中那样在每个顶级类中解析它们。

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


12
2017-08-12 10:24



但看看上面的课程。我假设你会建议我让我的构造函数采用ICustomerBusiness?我从哪里构造它,因此构造函数注入生效。我必须从某个地方开始,对吧? - Niels Brinch
@NielsBrinch twoflower实际上已经回答了这个问题,'你真的应该只在一个地方访问Ninject内核 - 在组合根目录。 - Luke McGregor
@NielsBrinch我扩展了我的答案,希望它有所帮助。如果你准确指定了什么应用程序,我还可以写更多关于你的情况下组合根的内容。 - twoflower
@Niels:组合根意味着你将内核放在一个类中,从那里可以获得其他人。实际上,这意味着要么拥有共享类并公开内核,要么隐藏内核并公开解决依赖关系的方法(创建对象)。 - Wiktor Zychla
太好了,我们到了某个地方!让我们专注于那个班级 使用 CustomerBusinessConsumer。如果我正确理解了webapps的组合根建议,这将不得不以某种方式访问​​内核,可能通过工厂。这不会导致我最初发布的代码大致相同吗? - Niels Brinch


答案:


因为你正在使用 NinjectWebCommon,我假设您有某种Web应用程序。你真的应该只在一个地方访问Ninject内核 - 在 组成根。它是您构建对象图的地方,也是您唯一需要访问IoC容器的地方。要实际获得所需的依赖项,通常需要使用 构造函数注入

例如,对于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类中都有一个无参数构造函数,因此您无法从对象图的顶部一直到底部使用构造函数注入。

但经过咨询后 马克西曼关于在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中那样在每个顶级类中解析它们。

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


12
2017-08-12 10:24



但看看上面的课程。我假设你会建议我让我的构造函数采用ICustomerBusiness?我从哪里构造它,因此构造函数注入生效。我必须从某个地方开始,对吧? - Niels Brinch
@NielsBrinch twoflower实际上已经回答了这个问题,'你真的应该只在一个地方访问Ninject内核 - 在组合根目录。 - Luke McGregor
@NielsBrinch我扩展了我的答案,希望它有所帮助。如果你准确指定了什么应用程序,我还可以写更多关于你的情况下组合根的内容。 - twoflower
@Niels:组合根意味着你将内核放在一个类中,从那里可以获得其他人。实际上,这意味着要么拥有共享类并公开内核,要么隐藏内核并公开解决依赖关系的方法(创建对象)。 - Wiktor Zychla
太好了,我们到了某个地方!让我们专注于那个班级 使用 CustomerBusinessConsumer。如果我正确理解了webapps的组合根建议,这将不得不以某种方式访问​​内核,可能通过工厂。这不会导致我最初发布的代码大致相同吗? - Niels Brinch


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

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

注射的另一面是你需要控制结构。当使用MVC时,所有的连接都是内置在nuget上的MVC ninject包中


1
2017-08-12 10:24



我在其他地方使用构造函数注入,例如,存储库位于业务类的构造函数中,并且它工作得很好。但我必须从某个地方开始,对吧?我不能在只需要使用该类的类中使用构造函数注入。 - Niels Brinch
@NielsBrinch通常在你的应用程序中你有一个入口点,在webapps中围绕global.ascx,在控制台应用程序中围绕Program.Main,通常ninject配置在这个级别,并且下面的任何东西都是用ninject构造的。如果您使用的是webapp,那么会有一些软件包可以为您设置这些软件包,以便从内核中实例化。对于WPF和Silverlight来说,情况也是如此。如果您在另一个应用程序类型中,则可能需要从内核手动构建您的祖先对象 - Luke McGregor
我在一个webapp。我在相当于global.asax(App_Start / NinjectWebCommon)中构造内核。您是否建议我在此课程中创建CustomerBusiness等,并将它们放在以后使用的地方? - Niels Brinch
@NielsBrinch如果您使用的是webapp,实际上只有在您收到请求后才需要您的对象。此时,ninject将实例化或重用您的CustomerBusiness以在该请求中使用 - Luke McGregor
这是什么样的?认为代码隐藏没有构造函数,需要使用CustomerBusiness。 - Niels Brinch