zoukankan      html  css  js  c++  java
  • 第 6 章 —— 依赖项注入(DI)容器 —— Ninject

        有些读者只想理解 MVC 框架所提供的特性,而不想介入开发理念与开发方法学。笔者不打算让你改变 —— 这属于个人取向,而且你知道交付优质项目需要的是什么。

        建议你至少粗略第看一看本章的内容,以明白哪些是有用的,但如果你不是单元测试型的人,那么可以跳到下一章,看看如何建立一个真实的 MVC 应用程序示例。

    6.1 准备示例项目

        创建新项目,项目名称为 “EssentiaTools”,整个这一章都将使用这一项目。(使用 “ASP.NET MVC Web Application”模板,选择 “空”选项并选中 “MVC”复选框)

      6.1.1 创建模型类

        在项目的 Models 文件夹中添加一个名为 Product.cs 的类文件,内容与之前几章的一样(除了命名空间)。

        添加一个类(LinqValueCalculator.cs),它将计算 Product 对象集合的总价

          该类定义了一个单一的方法 ValueProducts它使用 LINQ 的 Sum 方法将传递给该方法的可枚举对象中的每一个 Product 对象的 Price 属性值加和在一起这是笔者经常使用的一个很好的 LINQ 特性

          public  decimal  ValueProducts( IEnumerable<Product>  products )
          {
            return products.Sum( p => p.Price) ;
          }

        最后一个模型类叫做 ShoppingCart它表示了 Product 对象的集合,并且使用 LinqValueCalculator 来确定总价。(创建一个名称为 ShoppingCart.cs 的新类文件)

            public class ShoppingCart
            {
              private  LinqValueCalculator  calc;  //字段
              public ShoppingCart(LinqValueCalculator  calcParam)  //初始化器
              {
                calc = calcParam;
              }
    
              public IEnumerable<Product> Products { get; set; }    //属性
    
              public decimal CalculateProductTotal()          //方法
              {
                return calc.ValueProducts(Products);
              }
            }

      6.1.2 添加控制器

          private Product[ ] products = { … new Product { …… } … };

          public ActionResult Index()

          {

             LinqValueCalculator  calc = new LinqValueCalculator();  //该类真正实现了求和

            ShoppingCart cart = new ShoppingCart(calc) { Product = products };
            decimal  totalValue = cart.CalculateProductTotal();

            return View(totalValue);

          }

      6.1.3 添加视图

        (最后给项目添加的是视图)

        @model decimal ……

    6.2 使用 Ninject

        第 3 章介绍了 DI。其思想是对 MVC 应用程序中的组件进行解耦,这是通过接口与 DI 容器相结合来实现的

        DI 容器通过创建对象所依赖的接口并将其注入构造器以实现创建对象实例。

      6.2.1 理解问题

        在该示例应用程序中,ShoppingCart 类与 LinqValueCalculator 类是紧耦合的,而 控制器类(HomeController)与 ShoppingCart 和 LinqValueCalculator 都是紧耦合的。

        这意味着,如果想替换掉 LinqValueCalculator 类,就必须在与它有紧耦合关系的类中找出对它的引用,并进行修改。

        在一个实际项目中,这可能会成为一个乏味而易出错的过程。(尤其是,如果想在两个不同的计算器实现之间进行切换(例如用于测试),而不只是用另一个类来替换的情况下)

        运用接口

        通过使用 C# 接口,从计算器中抽象出其功能定义,我们可以解决部分问题。

        在 “Models” 文件夹中添加一个 IValueCalculator.cs 类文件(接口文件)

            public interface IValueCalculator

            {

              decimal ValueProducts( IEnumerable<Product>  products );

            }

        然后可以在 LinqValueCalculator 类中实现这一接口。(区别在于 “ : IValueCalculator”)

        该接口让笔者能够打断 ShoppingCart 与 LinqValueCalculator 类之间的紧耦合关系。—— 修改 ShoppingCart.cs

            private IValueCalculator calc;

            public ShoppingCart( IValueCalculator calcParam)

            {

              cale = calcParam;

            }

        笔者已经取得了一些进展,但是,C# 要求在接口实例化时要指定其实现类,因为它需要知道笔者想用的是哪一个实现类。—— 这意味着,Home 控制器在创建 LinqValueCalculator 对象时仍有问题。—— IValueCalculator calc = new LinqValueCalculator ();

        使用 Ninject 的目的就是要解决这一问题,用以对 IValueCalculator 接口的实现进行实例化 —— 但所需的实现细节不是 Home 控制器代码的一部分。(意即,通过 Ninject,可以去掉 Home 控制器中的这行粗体语句所示的代码,这项工作由 Ninject 来完成,这样便去掉了 Home 控制器与总价计算器 LinqValueCalculator 之间的耦合)

        这意味着要告诉 Nonject,笔者希望用 LinqValueCalculator 来实现 IValueCalculator 接口。

        并且要修改 HomeController 类,以使它能够通过 Ninject 而不是用 new 关键字来获取对象。

      6.2.2 将 Ninject 添加到 Visual Studio 项目

        在 Visual Studio 中选择 “工具” → “库包管理器” → “包管理器控制台”,并输入以下命令:

    Install-Package Ninject -version 3.0.1.10
    Install-Package Ninject.Web.Common -version 3.0.0.7
    Install-Package Ninject.MVC3 -version 3.0.0.6

        第一行命令用于安装 Ninject 内核包,其他命令用于安装内核的扩展包,以使 Ninject 能更好地与 ASP.NET 协同工作。

        (为了安装特定版本,这里使用了 version 参数。为了确保得到本章示例的正确结果,你应该使用 version 参数,但在实际项目中可以省略该参数,并获得最新版本)

      6.2.3 Ninject 初步

        为了得到 Ninject 的基本功能,要做的工作分为 3 个阶段。(依赖项注入可能需要花一段时间去理解,不要跳过任何细节,这样有助于减少困惑)

        在 Index 方法中添加基本的 Ninject 功能:

    using Ninject;
    
    public ActionResult Index()
    {
        IKernel ninjectKernel = new StandardKernel();        //第一个阶段是创建一个 Ninject 的内核(Kernel)实例(内核对象),Kernel 内核对象负责解析依赖项并创建新的对象 —— 当需要一个对象时,将使用这个内核而不是使用 new 关键字。
        ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();    //第二个阶段:配置Ninject内核,使其理解笔者用到的每一个接口所希望使用的实现对象。
        IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();       //第三个阶段:使用Ninject的内核对象调用其 Get方法来创建对象
    …… }

        ① 笔者需要创建一个 Ninject.IKernel 接口的实现,可以通过创建一个 StandardKernel 类的新实例来完成

        (对 Ninject 进行扩展和定制,可以使用不同种类的内核,但本章只需要这个内置的 StandardKernel(标准内核)—— 事实上,笔者使用 Ninject 已经好多年了,到目前为止只使用了这个 StandardKernel)

        ② Ninject 使用泛型创建了一种关系:将想要使用的接口设置为 Bind 方法的类型参数并在其返回的结果上调用 To 方法。将希望实例化的实现类设置为 To 方法的类型参数。如:

            ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();    // 该语句告诉 Ninject,IValueCalculator 接口的依赖项应该通过创建 LinqValueCalculator 类的实例来进行解析。

        ③ 最后一个步骤是使用 Ninject 来创建一个对象,其做法是调用内核的 Get 方法。—— Get 方法所使用的类型参数告诉 Ninject 笔者感兴趣的是哪一个接口,而该方法的结果是刚才用 To 方法指定的实现类型的一个实例。

    6.2.4 建立 MVC 的依赖项注入

        在后面的几小节中,会向你展示如何将 Ninject 嵌入到示例应用程序的核心,这将让笔者能够对控制器进行简化、扩展 Ninject 所具有的影响,以使它能够运用于不同的应用程序,并在控制器中去掉这些配置

      1. 创建依赖项解析器

        要做的第一个修改是创建一个自定义的依赖项解析器。—— MVC 框架需要使用依赖项解析器来创建类的实例,以便对请求进行服务。

        通过创建自定义解析器,可以保证 MVC 框架在任何时候都能使用 Ninject 创建一个对象 —— 包括控制器实例

        为了创建这个解析器,笔者创建了一个新的文件夹,名称为 Infrastructure,用于放置 MVC 应用程序中不适合放在其他文件夹的类。在该文件夹中添加了一个新的类文件,名称为 NinjectDependencyResolver.cs,文件内容如下:

    using System.Web.Mvc;
    using
    EssentialTools.Models; //项目名称.Models using Ninject; …… public class NinjectDependencyResolver : IDependencyResolver    //NinjectDependencyResolver 类实现了 IDependencyResolver 接口,它属于 System.Mvc 命名空间,也由 MVC 框架用于获取其所需的对象。 {   private IKernel kernel;   public NinjectDependencyResolver(IKernel kernelParam)   {     kernel = kernelParam;     AddBindings();   }
      // MVC 框架在需要类实例(以便对一个传入的请求进行服务)时,会调用 GetService 或 GetServices 方法
      public object GetService(Type serviceType)   {     return kernel.TryGet(serviceType);   }   public IEnumerable<object> GetServices(Type serviceType)   {     return kernel.GetAll(serviceType);   }   private void AddBindings()   {     kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();    //对于不同的应用,只需在这里修改绑定信息   } }

        MVC 框架在需要类实例(以便对一个传入的请求进行服务)时,会调用 GetService 或 GetServices 方法。—— 依赖项解析器要做的工作便是创建这一实例。(这是一项要通过调用 Ninject 的 TryGet 和 GetAll 方法来完成的任务)

        (GetAll 方法支持对单一类型的多个绑定,当有多个不同的服务提供器可用时,可以使用它)

        上述依赖项解析器类也是建立 Ninject 绑定的地方。—— 在 AddBindings 方法中,笔者用 Bind 和 To 方法配置了 IValueCalculator 接口和 LinqValueCalculator 类之间的关系

      2. 注册依赖项解析器(创建后还要进行注册)

        仅仅简单地创建一个 IDependencyResolver 接口的实现是不够的,还必须告诉 MVC 框架需要使用它。—— 笔者用 NuGet 添加的 Ninject 包在 App_Start 文件夹中创建了一个名称为 NinjectWebCommon.cs 的文件,它定义了应用程序启动时会自动调用的一些方法,目的是将它们集成到 ASP.NET 的请求生命周期之中(其目的是提供本章稍后会描述的“作用域”特性)。

        在 NinjectWebCommon 类的 RegisterServices 方法中,笔者添加了一条语句用于创建一个 NinjectDependencyResolver 类的实例,并用 System.Web.Mvc.DependencyResolver 类定义的 SetResolver 静态方法将其注册为 MVC 框架的解析器

    private static void RegisterServices(IKernel kernel)
    {
      System.Web.Mvc.DependencyResolver.SetResolver(new EssentialTools.Infrastructure.NinjectDependencyResolver(kernel));
    }

      3. 重构 Home 控制器

        最后一个步骤是重构 Home 控制器。

    ……
    public class HomeController : Controller
    {
      private IValueCalculator calc;
      private Product[] products = { …… };
    
      public HomeController(IValueCalculator calcParam)
      {
        calc = calcParam;
      }
    
      public ActionResult Index()
      {
        ShoppingCart cart = new ShoppingCart(calc) { Products = products};
        ……
      }
    }

        所做的主要修改是添加了一个类构造器,用于接收 IValueCalculator 接口的实现。(即修改 HomeController 类,使其声明一个依赖项

        Ninject 会在创建该控制器实例时,使用已经建立起来的配置,为该控制器创建一个实现 IValueCalculator 接口的对象

        所做的另一个修改是从控制器中删除了任何关于 Ninject 或 LinqValueCalculator 类的代码。(最终,笔者打破了 HomeController 与 LinqValueCalculator 类之间的紧耦合)

      以上创建的是一个构造器注入示例。(这是依赖项注入的一种形式)

        这里所采取的办法其好处之一是,任何控制器都可以在应用程序中声明一个解析器,并由 MVC 框架使用 Ninject 来实现

        所得到的最大好处是,在希望用另一个实现来代替 LinqValueCalculator 时,只需要对依赖项解析器类进行修改

  • 相关阅读:
    Openwrt智能路由系统开发--内容总结
    EdgeX foundry树莓派搭建与MQTT通讯
    C语言学习截图保存
    使用python生成Latex公式语法
    数据结构与算法之排序
    数据结构与算法之常用数据结构
    MySQL数据库规范 (设计规范+开发规范+操作规范)
    动态规划之LIS(最长上升子序列)
    动态规划之区间DP
    动态规划之状态压缩DP
  • 原文地址:https://www.cnblogs.com/zhangchaoran/p/7602138.html
Copyright © 2011-2022 走看看