这里主要介绍三类工具之一的 依赖项注入(DI)容器,其他两类 单元测试框架和模仿工具以后介绍。

1、准备示例项目

从创建一个简单的示例开始,名称为"EssentialTools" ,使用MVC空模板,如下所示:

创建模型类

在 Models 文件夹中添加一个名为 Products.cs 的类,添加内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class Product
    {
        public int ProductId { get; set; }

        public string Name { set; get; }

        public string Description { get; set; }

        public decimal Price { set; get; }

        public string Category { get; set; }

    }
}

再添加一个类,计算Product对象集合总价,名称为 LinqValueCalculator.cs ,添加内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class LinqValueCalculator
    {
        public decimal ValueProducts(IEnumerable<Product> products)
        {
            return products.Sum(p => p.Price);
        }
    }
}

LinqValueCalculator 类定义了一个单一的方法Value Products;他使用了LINQ的Sum方法将传递给该方法的可枚举对象中每一个Product对象的Price 属性值在一起。

最后一个模型类称为 ShoppingCart ,表示 Product对象集合,并且使用LinqValueCalculator 来确定总价。添加内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class ShoppingCart
    {
        private LinqValueCalculator calc;
        public ShoppingCart(LinqValueCalculator calcParam)
        {
            calc = calcParam;
        }

        public IEnumerable<Product> Products{ get; set; }

        public decimal CalculateProductToal()
        {
            return calc.ValueProducts(Products) ;
        }
    }
}

添加控制器

在 Controllers 文件夹下添加一个名称为 “HomeController”的新控制器,设置内容如下,Index 动作方法创建了一个“Product”对象数组,并使用 LinqValueCalculator 对象产生的值,将其传递给View方法。如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EssentialTools.Models;

namespace EssentialTools.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home

        private Product[] products =
           {
                new Product {Name = "Kayak",Description = "Watersports",Category = "Watersport",Price = 122M },
                new Product {Name = "Lifejacket",Description = "Watersports",Category = "Watersports",Price = 162M },
                new Product {Name = "Soccer ball",Description = "Soccer",Category = "Soccer",Price = 172.25M },
                new Product {Name = "Corner flag",Description = "Soccer",Category = "Soccer",Price = 82.15M }
            };
        public ActionResult Index()
        {
            LinqValueCalculator lvc = new LinqValueCalculator();
            ShoppingCart sc = new ShoppingCart(lvc){ Products = products };
            decimal totalValue = sc.CalculateProductToal();
            return View(totalValue);
        }
    }
}

添加视图

最后给项目添加视图,选中Index 右键添加视图,设置内容如下所示:

@model decimal
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div> 
        总价格为:$@Model
    </div>
</body>
</html>

这个视图使用@Model 表达式显示了从动作方法传递过来的 decimal 值。如果启动项目,会看到LinqValueCalculator 类计算的总值。

右键视图,在浏览器中查看:

2、使用Ninject

ASP.NET + MVC5 入门完整教程三 (下) ---MVC 松耦合 中介绍了DI,这里主要介绍如何使用Ninject。

理解问题

       创建一个示例,它含有用 DI 解决的基本问题:紧耦合类,ShoppingCart 类与 LinqValueCalculator 类是紧耦合的,而HomeController 类与 ShoppingCart 类和 LinqValueCalculator 类都是紧耦合的。这意味着替换 LinqValueCalculator 类,就必须在与他有紧耦合关系的类中找出对他的引用并进行修改。如果项目比较复杂,这就是个麻烦事。

运用接口

        通过使用C#接口,从计算器实现中抽象出其功能定义。在“Models”文件夹下添加一个“IValueCalculator.cs”类,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public interface IValueCalculator
    {
        decimal ValueProducts(IEnumerable<Product> products);
    }
}

然后可以在 LinqValueCalculator 类中实现这一接口。如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class LinqValueCalculator:IValueCalculator
    {
        public decimal ValueProducts(IEnumerable<Product> products)
        {
            return products.Sum(p => p.Price);
        }
    }
}

这个接口可以打断 ShoppingCart 类与 LinqValueCalculator 类是紧耦合关系。在 ShoppingCart 中运用该接口,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class ShoppingCart
    {
        //private LinqValueCalculator calc;

        //public ShoppingCart(LinqValueCalculator calcParam)
        //{
        //    calc = calcParam;
        //}

        private IValueCalculator calc;
        public ShoppingCart(IValueCalculator calcParam)
        {
            calc = calcParam;
        }
        public IEnumerable<Product> Products{ get; set; }

        public decimal CalculateProductToal()
        {
            return calc.ValueProducts(Products) ;
        }
    }
}

      上述过程已经解除了ShoppingCart 与 ValueCalculator 之间的耦合,因为在使用 ShoppingCart时,只要为其构造器传递一个 ICalculator 接口对象就行了,于是, ShoppingCart类与 IValuecalculator 的实现类之间不再有直接联系, 但是C#要求在接口实例化时要指定其实现类,这很好理解,因为它需要知道程序想用的是哪一个实现类。这意味着,Home控制器在创建 LinqvalueCalculator 对象时仍有问题,如下所示:

  public ActionResult Index()
        {
            //LinqValueCalculator lvc = new LinqValueCalculator();
            IValueCalculator lvc = new LinqValueCalculator();//可通过引入 Ninject 而简化
            ShoppingCart sc = new ShoppingCart(lvc) { Products = products };
            decimal totalValue = sc.CalculateProductToal();
            return View(totalValue);
        }
       使用 Ninject的目的就是要解决这一问题,用以对 IValueCalculator 接口的实现进行实例化,但所需的实现细节不是 Home控制器代码的一部分(意即,通过 Ninject,可以去掉Home控制器中的可去行所示的代码,这项工作由 Ninject来完成,这样便去掉了 Home 控制器与总价计算器 LinqValueCalculator之间的耦合)意味着要告诉 Ninject,LinqValueCalculator 是程序希望它用于 IValueCalculator 接口的实现,并且要修改 HomeController 类,以使它能够通过 Ninject 而不是用 new 关键字来获取对象。

将 Ninject 添加到 Visual Studio项目

       将Ninject添加到MvC项目最简单的方式,是使用 Visual studio对 NuGet的集成支持,这使 Visual Studio 易于安装各种各样的包,并保持这些包为最新。在 Visual studio中选择“Tool(工具)”→“ Library Package Manager(库包管理器)”→“ Package Manager Console(包管理器控制台)”,如下所示:

       安装完成上图三个插件,都更新到最新版本,保证前面都是绿色勾,及说明版本已经匹配,这时候在App_Start 文件夹下会出现 Ninject.Web.Common 文件。这说明Ninject 已经成功引入项目工程,可以使用了。打开 Ninject.Web.Common 文件,有两个错误需要修改,其实是少了一个命名空间引用,加入 using Ninject.Web.Common.WebHost 即可。

Ninject 初步

为了 使用 Ninject 的基本功能,要做 3 个 阶段工作。在 Home 控制器文件中做如下修改:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EssentialTools.Models;
using Ninject;

namespace EssentialTools.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home

        private Product[] products =
           {
                new Product {Name = "Kayak",Description = "Watersports",Category = "Watersport",Price = 122M },
                new Product {Name = "Lifejacket",Description = "Watersports",Category = "Watersports",Price = 162M },
                new Product {Name = "Soccer ball",Description = "Soccer",Category = "Soccer",Price = 172.25M },
                new Product {Name = "Corner flag",Description = "Soccer",Category = "Soccer",Price = 82.15M }
            };
        public ActionResult Index()
        {
            //LinqValueCalculator lvc = new LinqValueCalculator();
            //IValueCalculator lvc = new LinqValueCalculator();

            IKernel ninjectKernel = new StandardKernel();
            ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            IValueCalculator lvc = ninjectKernel.Get<IValueCalculator>();
            ShoppingCart sc = new ShoppingCart(lvc) { Products = products };
            decimal totalValue = sc.CalculateProductToal();
            return View(totalValue);
        }
    }
}

       第一个阶段是准备使用 Ninject。为此,创建一个 Ninject的内核( Kernel)实例,该实例是一个对象(内核对象),它负责解析依赖项并创建新的对象(为依赖项创建的对象)。当需要一个对象时,将使用这个内核而不是使用new关键字。以下是该清单中创建内核的语句:

 IKernel ninjectKernel = new StandardKernel();

       我们需要创建一个 Ninject. IKernel 接口的实现,可通过创建一个 StandardKernel 类的新实例来完成。对 Ninject 进行扩展和定制,可以使用不同种类的内核,但本次只需要这个内置的 StandardKerne1(标准内核)。事实上 Ninject 常用的也就是这个 StandardKernel。

       第二个阶段就是配置 Ninject 内核,以下是配置语句:

 ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
       Ninject 使用 C# 的类型参数创建了一种关系:将想要使用的接口设置为 Bind 方法的类型参数,并在其返回的结果上调用To方法。将希望实例化的实现类设置为To方法的类型参数。该语句告诉 Ninject,IValueCalculator 接口的依赖项应该通过创建 LinqValueCalculator 类的实例来进行解析。最后一个步骤是使用 Ninject来创建一个对象,其做法是调用内核的Get方法,如下所示:
 IValueCalculator lvc = ninjectKernel.Get<IValueCalculator>();
      Get 方法所使用的参数类型告诉 Ninject ,程序运行的是哪一个接口,而该方法的结果是刚才用 To 方法指定的实现类型的一个实例。

建立 MVC 的依赖项注入

创建依赖项解析器

      要做的第一个修改是创建一个自定义的依赖项解析器。MVC框架需要使用依赖项解析器来创建类的实例,以便对请求进行服务。通过创建自定义解析器,便能保证MVC框架在任何时候都能使用 Ninject创建一个对象—包括控制器实例。为了创建这个解析器,先创建了一个新的文件夹,名称为 Infrastructure,用于放置MVC应用程序中不适合放在其他文件夹的类。在该文件夹中添加一个新的类文件,名称为 NinjectDependencyResolver.cs,文件内容如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EssentialTools.Models;
using System.Web.Mvc;
using Ninject;
namespace EssentialTools.Infrastructure
{
    public class NinjectDependencyResolver : IDependencyResolver
    {
        private IKernel kernel;
        public NinjectDependencyResolver(IKernel kekrnelParam)
        {
            kernel = kekrnelParam;
            AddBindings();
        }   
        private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();         
        }

        public object GetService(Type serviceType)
        {
            return kernel.TryGet(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return kernel.GetAll(serviceType);
        }
    }
}

直接写 :IDependencyResolver 会出现错误,此时选中 IDependencyResolver 如下所示:

      自动生成 GetService 和 GetServices 方法,在按照上面代码修改GetService 和 GetServices 方法,前提是已经引入下面的命名空间。

using Ninject;
using System.Web.Mvc;

      NinjectDependencyResolver 类实现了 IDependencyResolver接口,它属于 System.Web.MVC 命名空间,也由MVC框架用于获取其所需的对象。MVC框架在需要类实例以便对一个传入的请求进行服务时,会调用 Getservice或 Eservices方法。依赖项解析器要做的工作便是创建这一实例——这是一项要通过调用 Ninject的 TryGet 和 GetAll 方法来完成的任务。TryGet 方法的工作方式类似于前面所用的Get方法,但当没有合适的绑定时,它会返回 null,而不是抛出一个异常。GetAll 方法支持对单一类型的多个绑定,当有多个不同的服务提供器可用时,可以使用它。上述依赖项解析器类也是建立 Ninject 绑定的地方。在 AddBindings 方法中,笔者用 Bind 和To 方法配置了 IValueCalculator 接口和 LinqValueCalculator 类之间的关系。

注册依赖项解析器

       仅仅简单地创建一个 IDependencyResolver 接口的实现是不够的,还必须告诉 MVC 框架需要使用它。笔者用 NuGet添加的 Ninject 包在 App_Start 文件夹中创建了一个名称为 Ninject.Web.common.cs 的文件,它定义了应用程序启动时会自动调用的一些方法,目的是将它们集成到 ASP.NET 的请求生命周期之中(其目的是提供稍后描述的“作用域”特性)。在 NinjectWebCommon 类的 Registerservices 方法中,我们添加了一条语句,用于创建一个NinjectDependencyResolver 类的实例,并用 System.Web.MVC。DependencyResolver 类定义的 SetResolver 静态方法将其注册为 MVC 框架的解析器,如清下所示。如果对此无法完全理解不必担心。该语句的作用是为了在 Ninject 和 MVC 框架之间创建一个支持 DI 的桥梁。

  private static void RegisterServices(IKernel kernel)
        {
            System.Web.Mvc.DependencyResolver.SetResolver(new EssentialTools.Infrastructure.NinjectDependencyResolver(kernel));
        }     
重构 Home 控制器

最后一步就是重构 Home 控制器,以便利用前面所建立的功能,如下加粗所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EssentialTools.Models;
using Ninject;

namespace EssentialTools.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        private IValueCalculator lvc;

        private Product[] products =
           {
                new Product {Name = "Kayak",Description = "Watersports",Category = "Watersport",Price = 122M },
                new Product {Name = "Lifejacket",Description = "Watersports",Category = "Watersports",Price = 162M },
                new Product {Name = "Soccer ball",Description = "Soccer",Category = "Soccer",Price = 172.25M },
                new Product {Name = "Corner flag",Description = "Soccer",Category = "Soccer",Price = 82.15M }
            };
        public HomeController(IValueCalculator calcParam)
        {
            lvc = calcParam;
        }

        public ActionResult Index()
        {
            //LinqValueCalculator lvc = new LinqValueCalculator();
            //IValueCalculator lvc = new LinqValueCalculator();

            //IKernel ninjectKernel = new StandardKernel();
            //ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            //IValueCalculator lvc = ninjectKernel.Get<IValueCalculator>(); //三步配置
            ShoppingCart sc = new ShoppingCart(lvc) { Products = products };
            decimal totalValue = sc.CalculateProductToal();
            return View(totalValue);
        }
    }
}
       所做的主要修改是添加了一个类构造器,用于接收 IValueCalculator 接口的实现,即修改 HomeController 类,使其声明一个依赖项。Ninject会在创建该控制器实例时,使用在HomeController中建立起来的配置,为该控制器创建一个实现 IValueCalculator 接口的对象所做的另一个修改是从控制器中删除了任何关于 Ninject 或 LinqValueCalculator 类的代码。最终,打破了 Homecontroller 与 LinqValueCalculator 类之间紧耦合。
       右键 Views 视图下的 Index.cshtml ,在浏览器中查看,可以看到如下所示效果:

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

创建依赖项链

       当要求 Ninject 创建一个类型时,它会检查该类型所声明的依赖项。它也会考查这些依赖项,看其是否依赖于其他类型—换句话说,它们是否还声明了自己的依赖项。如果有额外的依赖项 Ninject 会自动地解析这些依赖项,并创建所需要的所有类的实例,以这种方式处理依赖项链,最终更能够创建所需类型的实例(本段描述了 Inject处理依赖项链的工作方式)。为了演示这一特性,在项目的 Models文件夹中添加了一个名称为 Discount.cs文件。并用它定义了一个新的接口及其实现类,如下所示

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public interface IDiscountHelper
    {
        decimal ApplyDiscount(decimal totalParam);
    }
    public class DefaultDiscountHelper : IDiscountHelper
    {      
        public decimal ApplyDiscount(decimal totalParam)
        {
            return (totalParam - (10M / 100M * totalParam));
        }
    }
}

      IDiscountHelper 定义了ApplyDiscount 方法,他将一个折扣运用于一个十进制的值。DefaultDiscountHelper  类实现了IDiscountHelper 接口,并运用固定的 10% 折扣,这里修改 LinqValueCalculator ,以使他执行计算时使用 IDiscountHelper  接口,如下粗体所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class LinqValueCalculator : IValueCalculator
    {     
        private IDiscountHelper discounter;

        private static int counter = 0;
        public LinqValueCalculator(IDiscountHelper discountParam)
        {
            discounter = discountParam;          
        }
        public decimal ValueProducts(IEnumerable<Product> products)
        {
            return discounter.ApplyDiscount(products.Sum(p => p.Price));
        }
    }
}

       新的构造器声明了一个 IDiscounthelper 接口的依赖项。这里将构造器所接收的实现对象赋给了一个字段,并在 ValueProducts 方法中使用了它,以便将一个折扣运用于 Product 对象的累计值如同对 IValueCalculator 所做的那样,在 NinjectDependencyResolver 类中用 Ninject 内核将 IDiscountHelper 接口与其实现类进行绑定,如下粗体所示。

   private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>();      
        }

        上述这一做法已经创建了一个依赖项链。此时 Home 控制器依赖于 IValueCalculator 接口,这里已经告诉 Ninject 用 LinqValueCalculator 类对该接口进行解析。LinqValueCalculator 类又依赖于 IDiscountHelper 接口,这里又告诉 Ninject 用 DefaultdiscountHelper 类对其进行解析。Ninject 能够平滑地解析这种依赖项链,创建所需的对象为每一个依赖项进行解析,于是最终能创建一个 HomeController 类的实例,从而对一个HTTP请求进行服务。

指定属性和构造器参数值

      在将接口与其实现进行绑定时,可以为属性提供一些值方面的细节,以便对 Inject创建的对象进行配置。为了演示这一特性,这里我修订了 DefaultDiscountHelper 类,以使它定义一个 DiscountSize 属性,将其用于计算折扣量,如下粗体所示:

namespace EssentialTools.Models
{
    public interface IDiscountHelper
    {
        decimal ApplyDiscount(decimal totalParam);
    }
    public class DefaultDiscountHelper : IDiscountHelper
    {
        public decimal DiscountSize { get; set; }       
        
        public decimal ApplyDiscount(decimal totalParam)
        {
            return (totalParam - (DiscountSize / 100M * totalParam));
        }
    }
}

     在告诉 Ninject 一个接口需要使用的是哪一个类时,可以用 WithPropertyValue 方法为DefaultDiscountHelper 类中的 DiscountSize 属性设置一个值。反映了对 NinjectDependencyResolver 类中的 AddBindings 方法所做的修改。注意,需要没置的属性名称是以字符串形式提供的。如下黑体所示:

  private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50M);      
        }

  右键 Views 视图下的 Index.cshtml ,在浏览器中查看,可以看到如下所示效果:

如果需要设置多个属性值,可以链接调用 WithPropertyValue 方法,以涵盖所有这些属性,也可以用构造器参数做同样的事,如下粗体所示,重写 Discount.cs 文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public interface IDiscountHelper
    {
        decimal ApplyDiscount(decimal totalParam);
    }
    public class DefaultDiscountHelper : IDiscountHelper
    {
        public decimal DiscountSize;
        public DefaultDiscountHelper(decimal discountParam)
        {
            DiscountSize = discountParam;
        }
        public decimal ApplyDiscount(decimal totalParam)
        {
            return (totalParam - (DiscountSize / 100M * totalParam));
        }
    }
}

      为了用 Ninject 绑定这个类,可以在 NinjectDependencyResolver 文件中修改 AddBindings 方法,使用 WithConstructorArgument 方法来指定构造器参数值。如下粗体所示:

  private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountSize", 50M);      
        }

       同样,可以将这些方法调用连接在一起,已提供多值,并于依赖项混合和匹配, Ninject 会判断程序需要,并依此来创建他。这里,我不仅将方法改为 WithConstructorArgument ,也将 DiscountSize 改成 discountSize,用来匹配C#参数命名规则(参数首字母小写,属性首字母大写)。

使用条件绑定

       Ninject 支持多个条件的绑定方法,这让程序能够指定内核用哪一个类对某一特定的接口进行响。为了演示这一特性,在示例项目的 Models 文件夹中添加了一个名称为 FlexibleDiscountHelper.cs 的新文件,其内容如下粗体所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class FlexibleDiscountHelper:IDiscountHelper
    {
        public decimal ApplyDiscount(decimal totalParam)
        {
            decimal discount = totalParam > 100 ? 70 : 25;
            return (totalParam - (discount / 100M * totalParam));
        }
    }
}

这个 FlexibleDiscountHelper 类会根据总额大小运用不同的折扣,于是我们只要对 IDiscountHelper 接口的实现类进行选择,也就是修改 NinjectDependencyResolver 的 AddBindings 方法,告诉 Ninject 如何使用它,如下粗体所示:

  private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountSize", 50M);
            kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedExactlyInto<LinqValueCalculator>();
        }

    上述新绑定指明,在 Ninject内核要创建一个 LinqValueCalculator 对象时,应该使用 FlexibleDiscountHelper 类作为 IDiscounthelper 接口的实现。注意,我在适当的位置留下了对 IDiscountHelper 的原有绑定。 Ninject 会尝试找出最佳匹配,而且这有助于对同一个类或接口采用个默认绑定,以便在条件判据不能得到满足时,让 Ninject能够进行回滚。 Ninject有许多不同的条件绑定方法,最有用的一些条件绑定列于下表:

设置对象作用域

      最后一个 Ninject 特性有助于调整 Ninject 所建对象的生命周期,以满足应用程序的需求。默认情况下, Ninject 会在每次请求一个对象时,为每个依赖项所需的各个对象创建一个新实例。为了演示所发生的情况,笔者修改了 LinqValueCalculator类的构造器,以便在每次创建个新实例时都向 Visual studio 的输出窗口写一条消息,如下粗体所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EssentialTools.Models
{
    public class LinqValueCalculator : IValueCalculator
    {     
        private IDiscountHelper discounter;

        private static int counter = 0;
        public LinqValueCalculator(IDiscountHelper discountParam)
        {
            discounter = discountParam;
            System.Diagnostics.Debug.WriteLine(string.Format("Instance {0} Created", ++counter));
        }
        public decimal ValueProducts(IEnumerable<Product> products)
        {
            return discounter.ApplyDiscount(products.Sum(p => p.Price));
        }
    }
}

      System.Diagnostics.Debug 类包含了一些用来写出调试信息的方法,而且笔者发现它们是很有用的,通过下列代码可以看出它是如何工作的。修改了Home控制器,它要求从 Ninject 获得 lValueCalculator接口的两个实现,如下所示:

 public HomeController(IValueCalculator calcParam,IValueCalculator calc2)
        {
            lvc = calcParam;
        }
运行整个工程,效果如下:


       反复地对 LinqValueCalculator 进行实例化不会有什么问题,但并非所有类都是这样。对于某些类,你会希望在整个应用程序中共享一个单一的实例。而对于另一些类,又会希望为 ASP.NET 平台所接收到的每个HTTP请求,都创建一个新的实例。 Ninject 让你能够使用一种叫做“作用域( Scope)”的特性来控制所创建对象的生命周期,这是在建立接口与其实现之间的绑定时,通过方法调用来表示的。从下面代码可以看出如何将最有用的作用域运用于MVC框架的应用程序:在NinjectDependencyResolver 中将“请求作用域( Request Scope)”运用于 LinqValueCalculator 类(注意,在以下代码中,对 InRequestScope 方法的调用就是运用“请求作用域”)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EssentialTools.Models;
using System.Web.Mvc;
using Ninject;
using Ninject.Web.Common;
namespace EssentialTools.Infrastructure
{
    public class NinjectDependencyResolver : IDependencyResolver
    {
        private IKernel kernel;
        public NinjectDependencyResolver(IKernel kekrnelParam)
        {
            kernel = kekrnelParam;
            AddBindings();
        }   
        private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>().InRequestScope();
            kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountSize", 50M);
            kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedExactlyInto<LinqValueCalculator>();
        }

        public object GetService(Type serviceType)
        {
            return kernel.TryGet(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return kernel.GetAll(serviceType);
        }
    }
}
       InRequestScope 扩展方法属于 Ninject.web.Common命名空间,这是告诉 Ninject,对于ASPNET所接收到的每一个请求,应该只创建 LinqValueCalculator类的一个实例。每一个请求都会获得各自独立的对象,但同一个请求中的多个依赖项将会用这个类的单一实例进行解析启动应用程序并查看 Visual studio输出窗口,便可以看到这种修改的结果,这表明 Ninject 仅创建了 LinqValueCalculator类的一个实例。如果刷新浏览器窗口但未重启应用程序,则会看到 Ninject 创建了第二个对象。 Ninject 提供了一系列不同的对象作用域。下表摘录了其中最有用的一些方法。

源代码下载: 点击打开链接
















Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐