有些读者只想理解 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 时,只需要对依赖项解析器类进行修改。

第 6 章 —— 依赖项注入(DI)容器 —— Ninject的更多相关文章

  1. .Net核心依赖项注入:生命周期和最佳实践

    在讨论.Net的依赖注入(DI)之前,我们需要知道我们为什么需要使用依赖注入 依赖反转原理(DIP): DIP允许您将两个类解耦,否则它们会紧密耦合,这有助于提高可重用性和更好的可维护性 DIP介绍: ...

  2. MVC进阶之路:依赖注入(Di)和Ninject

    MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类, ...

  3. 依赖注入(DI)和Ninject

    [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject 本文目录: 1.为什么需要依赖注入 2.什么是依赖注入 3.使用NuGet安装库 4.使用Ninject的一般步骤 5. ...

  4. 在WPF中使用.NET Core 3.0依赖项注入和服务提供程序

    前言 我们都知道.NET Core提供了对依赖项注入的内置支持.我们通常在ASP.NET Core中使用它(从Startup.cs文件中的ConfigureServices方法开始),但是该功能不限于 ...

  5. Spring:所有依赖项注入的类型

    一.前言 Spring文档严格只定义了两种类型的注入:构造函数注入和setter注入.但是,还有更多的方式来注入依赖项,例如字段注入,查找方法注入.下面主要是讲使用Spring框架时可能发生的类型. ...

  6. 依赖注入(DI)和Ninject,Ninject

    我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,I ...

  7. 什么是IOC和什么是AOP,依赖注入(DI)和Ninject,Ninject

    我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,I ...

  8. DI容器Ninject在管理接口和实现、基类和派生类并实现依赖注入方面的实例

    当一个类依赖于另一个具体类的时候,这样很容易形成两者间的"强耦合"关系.我们通常根据具体类抽象出一个接口,然后让类来依赖这个接口,这样就形成了"松耦合"关系,有 ...

  9. [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject

    本人博客已转移至:http://www.exblr.com/liam  为什么需要依赖注入 在[ASP.NET MVC 小牛之路]系列的理解MVC模式文章中,我们提到MVC的一个重要特征是关注点分离( ...

随机推荐

  1. Java系列笔记(5) - 线程

    我想关注这个系列博客的粉丝们都应该已经发现了,我一定是个懒虫,在这里向大家道歉了.这个系列的博客是在我工作之余写的,经常几天才写一小节,不过本着宁缺毋滥的精神,所有写的东西都是比较精炼的.这篇文章是本 ...

  2. Python Web学习笔记之面试TCP的15个问题

    网络协议那么多,为什么面试喜欢问TCP?原因无外乎两个:1.TCP协议直接与进程打交道,写网络程序要用:2.TCP协议设计十分精巧,在一个不可靠的IP网络上实现了可靠传输,因为精巧,掌握TCP的原理自 ...

  3. MemcacheQ安装

    一.memcacheq介绍 特性: 1.简单易用 2.处理速度快 3.多条队列 4.并发性能好 5.与memcache的协议兼容 6.在zend framework中使用方便 memcacheq依赖于 ...

  4. 05:ModelForm 数据验证 & 生成html & 数据库操作

    目录:Django其他篇 01:Django基础篇 02:Django进阶篇 03:Django数据库操作--->Model 04: Form 验证用户数据 & 生成html 05:Mo ...

  5. [echats] - EChats图表的使用

    从上图可以看到,信息是能被抽象化为图形展示的,也就是基本的图表,曲线(想想股票那种曲线,普及一下那个叫K线图,想起当初去北京面试炒股公司的时候了...),柱状图等. 而apache开源的echats正 ...

  6. setSupportActionBar()方法报错

    在Android开发中,使用ToolBar控件替代ActionBar控件,需要在java代码中使用setSupportActionBar()方法,如下: Toolbar toolbar = (Tool ...

  7. IDEA Spring-boot-devTools 无效解决办法二

    转载地址:Intellij IDEA 使用Spring-boot-devTools无效解决办法 相信大部分使用Intellij的同学都会遇到这个问题,即使项目使用了spring-boot-devtoo ...

  8. TypeScript基础学习

    什么是TypeScript? TypeScript是一种由微软开发的自由的和开源的编程语言,它是JavaScript的一个超集,扩展了JavaScript的语法. TypeScript支持任意浏览器, ...

  9. Spark与Flink大数据处理引擎对比分析!

    大数据技术正飞速地发展着,催生出一代又一代快速便捷的大数据处理引擎,无论是Hadoop.Storm,还是后来的Spark.Flink.然而,毕竟没有哪一个框架可以完全支持所有的应用场景,也就说明不可能 ...

  10. YOLO V1论文理解

    摘要 作者提出了一种新的物体检测方法YOLO.YOLO之前的物体检测方法主要是通过region proposal产生大量的可能包含待检测物体的 potential bounding box,再用分类器 ...