每个MVC程序员的军火库中,都有这三个工具:一个依赖注入(DI)容器,一个单元测试框架,一个模拟工具。

1.准备一个示例项目

创建一个ASP.NET MVC Web Application的Empty项目,命名为EssentialTools。

1.1. 创建模型类

在Models文件夹下,创建Product类。

  1. public class Product
  2. {
  3. public int ProductId { get; set; }
  4. public string Name { get; set; }
  5. public string Description { get; set; }
  6. public decimal Price { get; set; }
  7. public string Category { get; set; }
  8. }

为了计算一个Product集合的合计金额,需要在Models文件夹下再添加一个LinqValueCalculator的类。

  1. public class LinqValueCalculator {
  2. public decimal ValueProducts(IEnumerable<Product> products) {
  3. return products.Sum(p => p.Price);
  4. }
  5. }

这个类定义了一个叫做ValueProducts的方法,它使用Linq Sum方法,将可枚举对象中的每一个Product的Price属性的值,加在一起。

最后一个模型类是ShoppingCart,它代表一个Product对象的集合,并使用一个LinqValueCalculator来决定合计值。

  1. public class ShoppingCart {
  2. private LinqValueCalculator calc;
  3. public ShoppingCart(LinqValueCalculator calcParam) {
  4. calc = calcParam;
  5. }
  6. public IEnumerable<Product> Products { get; set; }
  7. public decimal CalculateProductTotal() {
  8. return calc.ValueProducts(Products);
  9. }
  10. }

1.2.添加控制器

添加一个HomeController

  1. public class HomeController : Controller {
  2. private Product[] products = {
  3. new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  4. new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  5. new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  6. new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  7. };
  8. public ActionResult Index() {
  9. LinqValueCalculator calc = new LinqValueCalculator();
  10. ShoppingCart cart = new ShoppingCart(calc) { Products = products };
  11. decimal totalValue = cart.CalculateProductTotal();
  12. return View(totalValue);
  13. }
  14. }

1.3.添加视图

添加一个Index视图

  1. <p>Total Price is :@Model</p>

2.使用Ninject

依赖注入(DI)用于对MVC程序中的各个组件解耦。通过接口和DI容器的组合,创建接口的实现,从而创建一个对象的实例。并且将他们注入到构造器。

我在例子中故意留了一个问题,下面我会解释它,并展示怎么用我喜欢的Ninject这个DI容器去解决它。

2.1.理解这个问题

在实例应用中,我创建了一个DI处理的基本问题:仅仅耦合的类。ShoppingCart类与LinqValueCalculator类紧紧耦合。HomeController类同时与ShoppingCart和LinqValueCalculator类紧紧耦合。

这意味着,如果我要替换LinqValueCalculator类,我不得不在与它紧紧耦合的类中,找到并改变对它的引用。这对于小项目来说,不是一个问题。但在一个真实的项目中,如果我想切换不同的calculator实现(为了测试、为了示例),比起仅仅用一个类替换另一个类,这样的操作是冗长乏味的,并且容易出错。

2.1.1.应用一个接口

用接口,可以解决部分问题。接口定义了一个从实例中抽象出来的计算功能。在Models文件夹中,添加一个接口。

  1. public interface IValueCalculator {
  2. decimal ValueProducts(IEnumerable<Product> products);
  3. }

在LinqValueCalculator类中实现它。

  1. public class LinqValueCalculator : IValueCalculator {
  2. public decimal ValueProducts(IEnumerable<Product> products) {
  3. return products.Sum(p => p.Price);
  4. }
  5. }

这个接口,可以让我们破解ShoppingCart类和LinqValueCalculator类之间的紧紧耦合。

  1. public class ShoppingCart {
  2. private IValueCalculator calc;
  3. public ShoppingCart(IValueCalculator calcParam) {
  4. calc = calcParam;
  5. }
  6. public IEnumerable<Product> Products { get; set; }
  7. public decimal CalculateProductTotal() {
  8. return calc.ValueProducts(Products);
  9. }
  10. }

到这里,已经做了一些进展。但是C#需要在接口初始化期间,为其制定一个实例类。这可以理解为,因为它需要知道我想要使用哪个实例类。但是,这意味着,在我创建LinqValueCalculator对象时,这个问题依然在HomeController中存在,它依然与LinqValueCalculator类紧紧耦合着。

  1. public ActionResult Index() {
  2. IValueCalculator calc = new LinqValueCalculator();
  3. ShoppingCart cart = new ShoppingCart(calc) { Products = products };
  4. decimal totalValue = cart.CalculateProductTotal();
  5. return View(totalValue);
  6. }

我用Ninject的目标,是我在一个地方指定我想要实例化的IValueCalculator接口的实现,但是在HomeController的代码中,不出现要使用哪个实现的细节。

这意味着告诉Ninject,LinqValueCalculator是IvalueCalculator接口的实现,我想让它使用并更新HomeController类,让该类通过Ninject获得他的对象,而不是使用new关键字。

2.2.添加Ninject到VS项目中

使用NuGet包管理器,添加Ninject,Ninject.Web.Common,Ninject.MVC3这三个包到项目中。它会添加MVC3的引用,导致报错。在引用中,删除MVC3的引用,就可以在MVC5环境下完美工作。

2.3.用Ninject开始

这里有三个步骤,让基本的Ninject功能开始工作。在HomeController中,添加对Ninject的引用,创建一个Ninject kernel的实例,用Ninject添加接口和实现的绑定,从Ninject获得接口的实现。

  1. public class HomeController : Controller {
  2. private Product[] products = {
  3. new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  4. new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  5. new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  6. new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  7. };
  8. public ActionResult Index() {
  9. IKernel ninjectKernel = new StandardKernel();
  10. ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
  11. IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
  12. ShoppingCart cart = new ShoppingCart(calc) { Products = products };
  13. decimal totalValue = cart.CalculateProductTotal();
  14. return View(totalValue);
  15. }
  16. }

第一步是准备Ninject。创建一个Ninject kernel的实例,它用来响应解决依赖,并创造新对象。当我需要一个对象时,我会使用kernel,而不是new关键字。

  1. IKernel ninjectKernel = new StandardKernel();

通过创建StandardKernel类的新的实例,我创建了一个Ninjnel接口的实现。Ninject可以被扩展和个性化,来使用不同类型的kernel,但我只需要内置的StandardKernel。

第二步,是配置Ninject kernel ,让它理解我想使用每个接口的哪个实现。

  1. ninjectKernel.Bind< IValueCalculator >().To< LinqValueCalculator >();

Ninject使用C#类型参数,来创建一个关系:将Bind方法的参数,设置为我想要使用的接口,并且在它返回的结果上,调用To方法。我将To方法的参数,设置为我想要实例化的那个接口的实现类。这个声明告诉Ninject,在IValueCalculator接口上的依赖,应该通过创建一个LinqValueCalculator类的实例来解决。最后一步,是使用Ninject的Get方法来创建一个对象。

  1. IValueCalculator calc = ninjectKernel.Get<IValueCalculator>() ;

Get方法的参数,告诉Ninject,我感兴趣的是哪个接口,并且该方法返回的结果,是我在To方法中指定的实现的一个实例。

2.4.设置MVC依赖注入

上面展示的三个步骤的结果,是关于实现类必须被实例化来履行Ninject中设置的IValueCalculator接口的请求的知识。当然,我们还没有改进我们的应用,因为剩下的定义在HomeController中,这以为和HomeController依然与LinqValueCalculator类紧紧耦合着。

接下来,我会展示如何在MVC应用的核心部分,嵌入Ninject。这会让我们简化Controller,扩展Ninject的影响,让他贯穿整个应用。最后从控制器中移除配置。

2.4.1创建依赖解决者

我要做的第一个改变,是做一个自定义的依赖解决者。MVC框架使用依赖解决者来创建它需要服务请求的类的实例。通过创建一个自定义的解决者,我能确保MVC框架,无论何时他创建对象时,都使用Ninject。

要设置解决者,我创建一个文件夹Infrastructure(基础建设),用它来放不适合放在其他文件夹下的类。在文件夹下,创建NinjectDependencyresolver类。

  1. public class NinjectDependencyResolver : IDependencyResolver {
  2. private IKernel kernel;
  3. public NinjectDependencyResolver(IKernel kernelParam) {
  4. kernel = kernelParam;
  5. AddBindings();
  6. }
  7. public object GetService(Type serviceType) {
  8. return kernel.TryGet(serviceType);
  9. }
  10. public IEnumerable<object> GetServices(Type serviceType) {
  11. return kernel.GetAll(serviceType);
  12. }
  13. private void AddBindings() {
  14. kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
  15. }
  16. }

NinjectDependencyResolver类实现IDependencyResolver接口,这个接口是System.Mvc命名空间的一部分,并且MVC框架用它来得到需要的对象。MVC框架在他需要一个类的实例来服务传入的请求时,会调用GetService或GetServices方法。依赖解决者的任务,是通过执行TryGet和GetAll方法来创建实例。TryGet方法工作方式和Get方法相似,但是当这里没有合适的绑定时,它返回null,而不是抛出一个异常。GetAll方法支持多个绑定到一个单一类型,它用于当有多个不同的实现对象可用时。

我的依赖解决者类,也是我设置我的Ninject binding的地方。在AddBindings方法中,我使用Bind和To方法,来配置IValueCalculator接口和LinqValueCalculator类之间的关系。

2.4.2.注册依赖解决者

仅仅是创建一个IDependencyResolver接口的实现是不够的。Ninject包会在App_Start文件夹下创建一个叫做NinjectWebCommon的文件,它里面定义了程序启动时的automatically的方法,用来整合进ASP.NET请求的生命周期。在NinjectWebCommon类中的RegisterServices方法,我添加了一个声明,来创建一个NinjectDependencyResolver类的实例。并且使用System.Web.Mvc.DependencyResolver类中的静态方法SetResolver,使用MVC框架注册解决者。这句声明创建了Ninject和MVC框架之间的桥梁,来支持DI。

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

2.4.3.重构HomeController

最后一步,是重构HomeController。在之前的章节,我们呢在它里面配置了一些先进的工具。

  1. public class HomeController : Controller {
  2. private IValueCalculator calc;
  3. private Product[] products = {
  4. new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
  5. new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
  6. new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
  7. new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
  8. };
  9. public HomeController(IValueCalculator calcParam) {
  10. calc = calcParam;
  11. }
  12. public ActionResult Index() {
  13. ShoppingCart cart = new ShoppingCart(calc) { Products = products };
  14. decimal totalValue = cart.CalculateProductTotal();
  15. return View(totalValue);
  16. }
  17. }

最主要的改变,是我在了一个构造器,让它接收一个IValueCalculator接口的实现,改变HomeControllr类,让它声明一个依赖。当Ninject创建一个controller的实例时,Ninject会使用我在NinjectDependencyResolver类中的配置,为controller提供一个IValueCalculator接口的实现。

另一个改变,是移除了controller中提及的Ninject或LinqValueCalculator类。最后,我破坏了HomeController和LinqValueCalculator类之间紧紧耦合。

我已经创建了一个构造器注入的例子,它是依赖注入的一种。当你运行示例应用,并且IE请求应用的根路径时,发生的:

1.MVC框架接收到请求,并计算出请求是希望加入Home控制器。

2.MVC框架请求我自定义的依赖解决者,依赖解决者会使用GetService方法的Type参数,指定要创建的类,创建一个HomeController类的新实例。

3.我的依赖解决者请求Ninject创建一个新的HomeController类,将Type对象传递给TryGet方法。

4.Ninject会检查HomeController的构造器,发现它有一个声明,依赖了它应景绑定的IValueCalculator接口。

5.Ninject创建一个LinqValueCalculator类的实例,并使用它创建一个HomeController类的心实例。

6.Ninject传递HomeController实例给自定义依赖解决者,它会将他返回给MVC框架。MVC框架使用控制器的实例来为请求服务。

我分析得这么细,是因为在你第一次使用DI时,觉得它有点离奇古怪令人费解。我这样做的一个益处,是程序中的任何控制器,都能声明一个依赖,并且MVC框架会使用Ninject来解决它。

最好的部分是,当我想要用其他实现替换LinqValueCalculator时,只需要修改依赖解决者类。因为这是唯一一个地方,我不得不指定我要使用IValueCalculator接口的实现。

Pro ASP.NET MVC 5 Framework.学习笔记.6.3.MVC的必备工具的更多相关文章

  1. Pro ASP.NET MVC 5 Framework.学习笔记.6.4.MVC的必备工具

    2.5.创建链式依赖 当你请求Ninject创建一个类型,它检查该类型的依赖是否声明.它也会检查该依赖是否依赖其他类型.如果这里有附加依赖,Ninject自动解决他们,并创建请求的所有类的实例.正是由 ...

  2. ASP.NET MVC Web API 学习笔记---第一个Web API程序

    http://www.cnblogs.com/qingyuan/archive/2012/10/12/2720824.html GetListAll /api/Contact GetListBySex ...

  3. ASP.NET Core Web开发学习笔记-1介绍篇

    ASP.NET Core Web开发学习笔记-1介绍篇 给大家说声报歉,从2012年个人情感破裂的那一天,本人的51CTO,CnBlogs,Csdn,QQ,Weboo就再也没有更新过.踏实的生活(曾辞 ...

  4. 一起学ASP.NET Core 2.0学习笔记(二): ef core2.0 及mysql provider 、Fluent API相关配置及迁移

    不得不说微软的技术迭代还是很快的,上了微软的船就得跟着她走下去,前文一起学ASP.NET Core 2.0学习笔记(一): CentOS下 .net core2 sdk nginx.superviso ...

  5. MVC缓存OutPutCache学习笔记 (二) 缓存及时化VaryByCustom

    <MVC缓存OutPutCache学习笔记 (一) 参数配置> 本篇来介绍如何使用 VaryByCustom参数来实现缓存的及时化.. 根据数据改变来及时使客户端缓存过期并更新.. 首先更 ...

  6. MVC缓存OutPutCache学习笔记 (一) 参数配置

    OutPutCache 参数详解 Duration : 缓存时间,以秒为单位,这个除非你的Location=None,可以不添加此属性,其余时候都是必须的. Location : 缓存放置的位置; 该 ...

  7. Entity Framework 学习笔记(2)

    上期回顾:Entity Framework 学习笔记(1) Entity Framework最主要的东西,就是自己创建的.继承于DbContext的类: /// <summary> /// ...

  8. Entity Framework学习笔记

    原文地址:http://www.cnblogs.com/frankofgdc/p/3600090.html Entity Framework学习笔记——错误汇总   之前的小项目做完了,到了总结经验和 ...

  9. thinkphp学习笔记7—多层MVC

    原文:thinkphp学习笔记7-多层MVC ThinkPHP支持多层设计. 1.模型层Model 使用多层目录结构和命名规范来设计多层的model,例如在项目设计中如果需要区分数据层,逻辑层,服务层 ...

随机推荐

  1. 私有IP地址范围

    私有IP地址范围 有一部分的地址专门是用于内网的地址,包括: A类中 10.0.0.0/8 B类中 172.16.0.0/12 ~ 172.31.0.0/12 C类中 192.168.0.0/16 这 ...

  2. Java数据库连接——JDBC基础知识(操作数据库:增删改查)

    一.JDBC简介 JDBC是连接java应用程序和数据库之间的桥梁. 什么是JDBC? Java语言访问数据库的一种规范,是一套API. JDBC (Java Database Connectivit ...

  3. MAC下Android的Eclipse开发环境的搭建

    一.Eclipse的下载 到网站:http://www.eclipse.org/downloads/ 上,由于我们是用Java开发的所以步骤如下: 1.  找到"Eclipse IDE fo ...

  4. Python_01 在DOS环境运行python程序

    >怎么在DOS环境运行一个python程序 >>在文本编辑器中编辑程序,最后保存成   文件名.py  的格式 >>在DOS界面下找到源程序所在的路径,然后用  pyth ...

  5. websocket 待更新

    https://mp.weixin.qq.com/s?__biz=MjM5OTM0MzIwMQ==&mid=2652545551&idx=1&sn=403b75d95cf191 ...

  6. UI-UIImageView的图片填充方式(contentMode)_图片作为控件背景图的拉伸方式(stretch)介绍

    常用图片填充方式 这里只介绍三个最常用的图片填充方式 UIViewContentModeScaleToFill模式会导致图片变形.例如: UIViewContentModeScaleAspectFit ...

  7. Linux bash运维操作日志审计(单服务器)

    目前公司有几台机器比较重要,需要把所有用户的操作记录下来,于是就是参照资料来完成 1. vim /etc/profile.d/oplogrc.sh logdir=/opt/oplog userdir= ...

  8. 【java开发系列】—— JDK安装

    前言 作为一个java开发者,安装JDK是不可避免的,但是配置路径却总是记不住,百度也有很多参考例子.这里仅仅当做以后参考的笔记记录. 说到JDK,就不得不提JRE.他们到底是什么呢? 通常我们进行j ...

  9. 认识javascript

    javascript小知识 www.phonegap.com(跨平台开发框架) Cocos2d-Html5(WebGL渲染  javascript语言) creatjs.com(融合了flash动画的 ...

  10. JSon_零基础_005_将po(bean)对象集合List转换为JSon格式的对象字符串,返回给界面

    将po(bean)对象集合List转换为JSon格式的对象字符串,返回给界面 导入jar包: 编写:po(bean)代码: package com.west.webcourse.po; /** * 第 ...