第 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 时,只需要对依赖项解析器类进行修改。
第 6 章 —— 依赖项注入(DI)容器 —— Ninject的更多相关文章
- .Net核心依赖项注入:生命周期和最佳实践
在讨论.Net的依赖注入(DI)之前,我们需要知道我们为什么需要使用依赖注入 依赖反转原理(DIP): DIP允许您将两个类解耦,否则它们会紧密耦合,这有助于提高可重用性和更好的可维护性 DIP介绍: ...
- MVC进阶之路:依赖注入(Di)和Ninject
MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类, ...
- 依赖注入(DI)和Ninject
[ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject 本文目录: 1.为什么需要依赖注入 2.什么是依赖注入 3.使用NuGet安装库 4.使用Ninject的一般步骤 5. ...
- 在WPF中使用.NET Core 3.0依赖项注入和服务提供程序
前言 我们都知道.NET Core提供了对依赖项注入的内置支持.我们通常在ASP.NET Core中使用它(从Startup.cs文件中的ConfigureServices方法开始),但是该功能不限于 ...
- Spring:所有依赖项注入的类型
一.前言 Spring文档严格只定义了两种类型的注入:构造函数注入和setter注入.但是,还有更多的方式来注入依赖项,例如字段注入,查找方法注入.下面主要是讲使用Spring框架时可能发生的类型. ...
- 依赖注入(DI)和Ninject,Ninject
我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,I ...
- 什么是IOC和什么是AOP,依赖注入(DI)和Ninject,Ninject
我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,I ...
- DI容器Ninject在管理接口和实现、基类和派生类并实现依赖注入方面的实例
当一个类依赖于另一个具体类的时候,这样很容易形成两者间的"强耦合"关系.我们通常根据具体类抽象出一个接口,然后让类来依赖这个接口,这样就形成了"松耦合"关系,有 ...
- [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject
本人博客已转移至:http://www.exblr.com/liam 为什么需要依赖注入 在[ASP.NET MVC 小牛之路]系列的理解MVC模式文章中,我们提到MVC的一个重要特征是关注点分离( ...
随机推荐
- 学写网页 #05# CSS Mastery 笔记 1~3
看到第四章才发现这本书已经太旧了..看到第 3 章为止吧.前三章主要讲的内容:一些编码常识.怎样选择元素.盒子模型(主要是 Margin).定位(绝对.相对.浮动.fixed 等) 第一章 conve ...
- 20145104张家明 《Java程序设计》第四次实验设计
20145104张家明 <Java程序设计>第四次实验设计 这第四次实验报告 我们开始着手安卓了 在电脑上安装了安卓虚拟机
- 20145222何志威《网络对抗》- Web安全基础实践
20145322何志威<网络对抗>Exp9 Web安全基础实践 基础问题回答 1.SQL注入原理,如何防御 SQL注入 就是通过把SQL命令插入到"Web表单递交"或& ...
- Android实验一(在Android Studio中创建项目和模拟器)
北京电子科技学院(BESTI) 实 验 报 告 课程:移动平台开发 班级:1592 姓名:苏泽楠 学号:20159207 成绩: 指导教师 ...
- JAVA I/O(四)网络Socket和ServerSocket
<Thinking in Enterprise Java>中第一章描述了用Socket和Channel的网络编程,核心即为Socket和Channel,本文简单讲述Socket的应用. S ...
- MBR主引导扇区解析2
unsigned AnsiChar data[] = { 0x33, 0xC0, 0x8E, 0xD0, 0xBC, 0x00, 0x7C, 0x8E, 0xC0, 0x8E, 0xD8, 0xBE, ...
- AndroidStudio V2.0.0.汉化
汉化包下载:http://pan.baidu.com/s/1kVKYUjH AndroidStudio V2.0.x.版汉化工作介绍 resource_en.jar------> resourc ...
- TeeChart的坐标轴
TeeChart一共有六个坐标轴,一下是默认值 tChart1.Axes.Bottom.Visible = true;//横轴 tChart1.Axes.Left.Visible = true;//纵 ...
- 【第一章】 第一个spring boot程序
环境: jdk:1.8.0_73 maven:3.3.9 spring-boot:1.2.5.RELEASE(在pom.xml中指定了) 注意:关于spring-boot的支持, 最少使用jdk7(j ...
- Codeforces Round #528 div1
完了,看来上一次的flag要应验了,我大概是真的要掉成pupil了吧.. A - Connect Three 这个就是找到x的中间值,y的中间值,然后切一下,然后把所有的点挂到这条边上.但是我做的还是 ...