九:MVC基本工具
示例项目准备
1:新建空的MVC项目
2:在Models文件夹下新增三个类
Product类
namespace TestBlankMVC.Models { public class Product { public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
LinqValueCalculator类
using System.Collections.Generic; using System.Linq; namespace TestBlankMVC.Models { public class LinqValueCalculater { public decimal ValueProducts(IEnumerable<Product> products) { return products.Sum(p=>p.Price); } } }
ShoppingCart类
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace EssentialTools.Models { public class ShoppingCart { private LinqValueCalculater calc; public ShoppingCart(LinqValueCalculater calcParm) { calc = calcParm; } public IEnumerable<Product> Products { get; set; } public decimal CalculateProductTotal() { return calc.ValueProducts(Products); } } }
3:添加控制器
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using TestBlankMVC.Models; namespace TestBlankMVC.Controllers { public class HomeController : Controller { private Product[] products = { new Product{ProductID=1,Name="Kayak",Category="Water",Price=275M}, new Product{ProductID=2,Name="Lifejacket",Category="Water",Price=48.5M}, new Product{ProductID=3,Name="Soccer",Category="Soccer",Price=19.5M}, new Product{ProductID=4,Name="Corner flag",Category="Soccer",Price=34.65M} }; public ActionResult Index() { LinqValueCalculater calc = new LinqValueCalculater(); ShoppingCart cart = new ShoppingCart(calc) { Products = products }; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); } } }
4:添加视图
新增视图Index
@model decimal @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Value</title> </head> <body> <div>合计值是:@Model</div> </body> </html>
Ninject初步
5:安装Ninject包
打开Visual Studio功能菜单TOOLS的下拉菜单Library Package Manger->Package Manage Console输入下面命令
install-package Ninject
install-package Ninject.Web.Common
install-package Ninject.MVC3
第一行安装Ninject内核包,剩下两行安装拓展包。每行行命令还可以加后缀 -version如-version 3.0.1.10指定要安装的版本
6:添加接口
在Models文件下添加接口类
using System.Collections.Generic; namespace TestBlankMVC.Models { public interface IValueCalculator { decimal ValueProducts(IEnumerable<Product> products); } }
修改LinqValueCalculator,让该类实现接口IValueCalculator
public class LinqValueCalculater : IValueCalculator
修改ShoppingCart类如下
using System.Collections.Generic; namespace TestBlankMVC.Models { public class ShoppingCart { private IValueCalculator calc; public ShoppingCart(IValueCalculator calcParam) { calc = calcParam; } public IEnumerable<Product> Products { get; set; } public decimal CalculateProductTotal() { return calc.ValueProducts(Products); } } }
修改的内容就是把类LinqValueCalculator替换成接口IValueCalculator
7:使用Ninject
修改HomeController中的Index方法如下
public ActionResult Index() { //LinqValueCalculater calc = new LinqValueCalculater(); //ShoppingCart cart = new ShoppingCart(calc) { Products = products }; //decimal totalValue = cart.CalculateProductTotal(); IKernel kernel = new StandardKernel(); kernel.Bind<IValueCalculator>().To<LinqValueCalculater>(); IValueCalculator calc = kernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calc) { Products=products}; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); }
IKernel kernel = new StandardKernel();创建一个内核。这个内核负责解析依赖项并创建新的对象
kernel.Bind<IValueCalculator>().To<LinqValueCalculater>();接口希望得到的实例
IValueCalculator calc = kernel.Get<IValueCalculator>();把实例转换成接口,类似IvalueCalator calc= LinqValueCalculator
8:创建依赖性解析器
在项目中新增一个文件夹Infrastructure。在该文件夹下新增一个类NinjectDependencyResolver.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Ninject; using System.Web.Mvc; using TestBlankMVC.Models; namespace TestBlankMVC.Infrastructure { public class NinjectDependencyResolver:IDependencyResolver { private IKernel kernel; public NinjectDependencyResolver(IKernel kernelParam) { kernel = kernelParam; AddBindging(); } private void AddBindging() { kernel.Bind<IValueCalculator>().To<LinqValueCalculater>(); } public object GetService(Type serviceType) { return kernel.TryGet(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return kernel.GetAll(serviceType); } } }
NinjectDependencyResolver类实现了IDependencyResolver接口,它属于System.Mvc命名空间。
MVC框架在需要类实例是便传入一个请求进行服务,会调用GetServices或GetService方法,依赖解析器就会创建一个实例。这需要Ninject的TryGet和GetAll方法来完成。如果没有绑定,会返回null,而不是报错。
9:注册依赖项解析器
仅仅创建一个IDependencyResolver接口的实现是不够的,还要告诉MVC框架需要使用它。
在项目的App_Start文件夹中修改又Ninject自动创建一个NinjectWebCommon.cs文件。它定义了应用程序启动时会调用的方法。
目的是将它们集成到ASP.NET的请求声明周期中(其目的是作用域特性)
using Ninject; namespace TestBlankMVC.App_Start { public class NinjectWebCommon { private static void RegisterServices(IKernel kernel) { System.Web.Mvc.DependencyResolver.SetResolver(new TestBlankMVC.Infrastructure.NinjectDependencyResolver(kernel)); } } }
10:再次修改HomeController的代码如下
using EssentialTools.Models; using Ninject; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace EssentialTools.Controllers { public class HomeController : Controller { private IValueCalculator calc; public HomeController(IValueCalculator calcParm) { calc = calcParm; } private Product[] products = { new Product{ProductID=1,Name="Kayak",Category="Water",Price=275M}, new Product{ProductID=2,Name="Lifejacket",Category="Water",Price=48.5M}, new Product{ProductID=3,Name="Soccer",Category="Soccer",Price=19.5M}, new Product{ProductID=4,Name="Corner flag",Category="Soccer",Price=34.65M} }; public ActionResult Index() { //IKernel kernel = new StandardKernel(); //kernel.Bind<IValueCalculator>().To<LinqValueCalculater>(); //IValueCalculator calc = kernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calc) { Products = products }; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); } } }
到现在为止,一个完整的依赖注入的例子已经初步完成。
- 浏览器发出请求时,MVC根据路由规则会指向HomeController的Index方法,在请求时会创建HomeController实例
- 创建HomeController实例时,由于构造函数含有参数IValueCalculator,那么依赖项解析器就会对此依赖项进行解析。将接口指向依赖项解析器的GetService方法
- 依赖项解析器会将传递过来的类型参数交给TryGet方法,邀请Ninject创建一个新的HomeController实例
- Ninject检测构造函数与LinqValueCalculator有绑定关系,于是新建了LinqValueCalculator实例,并回递给依赖项解析器
- 依赖项解析器将Ninject返回的类作为接口实现类返回给MVC框架
- MVC创建成果HomeController类,其中构造函数为接口的实现类
11:创建依赖项链
Ninject创建一个类型时,会检测所有的依赖项,也会考察这些依赖项。也就是说如果依赖项还有额外的依赖项,Ninject会自动解析这些依赖项并创建实例。
在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 - totalParam / 1.17M; } } }
12:在LinqValueCalculator.cs中新增一个依赖项
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace EssentialTools.Models { public class LinqValueCalculater:IValueCalculator { private IDiscountHelper discounter; public LinqValueCalculater(IDiscountHelper helper) { discounter = helper; } public decimal ValueProducts(IEnumerable<Product> products) { return discounter.ApplyDiscount(products.Sum(p=>p.Price)); } } }
13:绑定-在NinjectDependencyResolver.cs中绑定接口及其实现
private void AddBindging() { kernel.Bind<IValueCalculator>().To<LinqValueCalculater>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); }
该绑定就创建了一个依赖项,HomeController依赖IValueCalculator,IValueCalculator解析成LinqValueCalculator,而它又依赖IDiscountHelper,Ninject又继续解析成DefaultDiscountHelper
14:构造器使用属性的绑定
如果IDiscountHelper新增了一个属性DiscounRate并使用了该属性来计算折扣,代码如下
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 DiscountRate; public DefaultDiscountHelper(decimal discountRateParm) { DiscountRate = discountRateParm; } public decimal ApplyDiscount(decimal totalParam) { return totalParam / (1+DiscountRate/100); } } }
这时候如果运行程序会报错,需要修改绑定
private void AddBindging() { kernel.Bind<IValueCalculator>().To<LinqValueCalculater>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountRateParm",17M); }
15:使用条件绑定
Ninject支持多个条件绑定方法,可以指定内核用哪一个类对接口进行相应
如在Models文件夹下新增一个类NewDiscountHelper实现IDiscountHelper接口,代码如下
namespace EssentialTools.Models { public class NewDiscountHelper:IDiscountHelper { public decimal ApplyDiscount(decimal totalParam) { decimal discount = totalParam > 100 ? 70 : 25; return (totalParam-(discount/100M*totalParam)); } } }
然后修改绑定函数如下
private void AddBindging() { kernel.Bind<IValueCalculator>().To<LinqValueCalculater>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountRateParm",17M); kernel.Bind<IDiscountHelper>().To<NewDiscountHelper>().WhenInjectedInto<LinqValueCalculater>(); }
可以看到IDiscountHelper有两个绑定,那么在该示例程序中会解析成哪个呢?Ninject内核会根据情况来判定,因为IvalueCalculator解析成了LinqValueCalculator,而IDiscountHelper有谓词条件,所以解析成了NewDiscountHelper
- When(谓词):当谓词(一个Lambda表达式)的结果是true时,实现绑定
- WhenClassHas<T>():当被注入的类一注解属性进行注释,类型为T时,实施绑定
- WhenInjectedInto<T>():当要注入的类型是T时,实施绑定
16:作用域
有时候希望解析的类在整个程序中共享单一实例,而有时候又希望每次收到Http请求都创建新实例。Ninject有一种使用方法叫做作用域(Scope)的特性,能够控制创建对象的生命周期。
修改绑定函数如下
private void AddBindging() { kernel.Bind<IValueCalculator>().To<LinqValueCalculater>().InThreadScope(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountRateParm",17M); kernel.Bind<IDiscountHelper>().To<NewDiscountHelper>().WhenInjectedInto<LinqValueCalculater>(); }
- InRequestScope():创建单一实例,用于解析一个Http请求中的各个对象(每个请求一个实例)
- InThreadScope():创建单一实例,用于解析一个线程中的各个对象(每线程一实例)
- InSingletonScope() ToConstane(Object):创建单一实例,共享整个应用程序
- InTransientScope():未指定作用域效果如何,为每一个被解析的依赖项创建一个对象(每个依赖项一个实例)