MVC开发之注入容器Ninject的使用
背景
/// <summary>/// 产品模型类/// </summary>public class Product{public int ProductID { get; set; }public string Name { get; set; }public string Description { get; set; }public decimal Price { get; set; }public string Category { get; set; }}/// <summary>/// 计算器类:使用LINQ的sum方法来计算产品的总价/// </summary>public class LinqValueCalculator{public decimal ValueProducts(IEnumerable<Product> products) {return products.Sum(x=>x.Price);}}/// <summary>/// 购物车类:表示Product对象的集合,并使用计算器类LinqValueCalculator来计算总价【Product集合作自动属性,利用构造器初始化计算器类,再定义一个方法,里面调用计算器类的方法】/// </summary>public class ShoppingCart{private LinqValueCalculator calc;public IEnumerable<Product> Products { get; set; }public ShoppingCart(LinqValueCalculator calc) {this.calc = calc;}public decimal CalculateProductTotal() {return calc.ValueProducts(Products);}}/// <summary>/// 控制器:要先实例化计算器类,再把计算器类作参数带入购物车类的构造器中,然后调用购物车类的方法/// </summary>public class HomeController : Controller{private Product[] products={new Product{Name="Kayak",Category="Watersports",Price=275m},new Product{Name="Lifejacket",Category="Watersports",Price=48.95m},new Product{Name="Soccer ball",Category="Soccer",Price=19.50m},new Product{Name="Corner flag",Category="Soccer",Price=34.95m}};public ActionResult Index(){LinqValueCalculator calc = new LinqValueCalculator();ShoppingCart shoppingCart = new ShoppingCart(calc) {Products=products};decimal result = shoppingCart.CalculateProductTotal();return View(result);}}
最后view视图呈现的结果:

在该项目中依赖一些紧耦合的类:ShoppingCart类与LinqValueCalculator类是紧耦合的,而HomeController类与ShoppingCart和LinqValueCalculator都是紧耦合的。这意味着,如果想替换LinqValueCalculator类,就必须在与它有紧耦合关系的类中找到对它的引用,并修改。这对于一个实际项目中,就是一个乏味且易错的过程。
运用接口
/// <summary>/// 接口:从计算器的实现中抽象出其功能定义/// </summary>public interface IValueCalculator{decimal ValueProducts(IEnumerable<Product> products);}/// <summary>/// 计算器类:继承接口/// </summary>public class LinqValueCalculator:IValueCalculator{public decimal ValueProducts(IEnumerable<Product> products) {return products.Sum(x=>x.Price);}}/// <summary>/// 购物车类/// </summary>public class ShoppingCart{//都改为传递接口,解除ShoppingCart与ValueCalculator之间的耦合private IValueCalculator calc;public IEnumerable<Product> Products { get; set; }public ShoppingCart(IValueCalculator calc){this.calc = calc;}public decimal CalculateProductTotal() {return calc.ValueProducts(Products);}}/// <summary>/// 控制器:要先实例化计算器类,再把计算器类作参数带入购物车类的构造器中,然后调用购物车类的方法/// </summary>public class HomeController : Controller{private Product[] products={new Product{Name="Kayak",Category="Watersports",Price=275m},new Product{Name="Lifejacket",Category="Watersports",Price=48.95m},new Product{Name="Soccer ball",Category="Soccer",Price=19.50m},new Product{Name="Corner flag",Category="Soccer",Price=34.95m}};public ActionResult Index(){//但是Home控制器和LinqValueCalculator类还是紧耦合关系IValueCalculator calc = new LinqValueCalculator();ShoppingCart shoppingCart = new ShoppingCart(calc) {Products=products};decimal result = shoppingCart.CalculateProductTotal();return View(result);}}
Ninject
Ninject的出现就是要解决Home控制器和计算器类LinqValueCalculator之间耦合的问题
1.将Ninject添加到Visual Studio项目中
选择“工具”-》“库包管理器”-》“管理解决方案的NuGet包”-》打开“NuGet包”对话框,点击左侧面板的“在线”,在右上角的搜索框中输入“Ninject”,将会找到一系列的Ninject包。本人实际开发中,遇到搜索不到的情况,这时点击左下角的设置按钮,弹出设置的对话框,把图中的两个数据源都勾选上,再回来搜索就可以找到了。


2.创建依赖解析器
示例要做的第一个修改就是创建一个自定义的依赖解析器。MVC框架需要使用依赖解析器来创建类的实例,以便对请求进行服务。
给项目添加一个名为InfraStructure的新文件夹,并添加一个名为NinjectDependencyResolver.cs的类。
该类分三步走
1)创建一个Ninject内核的实例StandardKernel,用它与Ninject进行通信;
2)应用实例的方法,建立程序中接口与实现类的绑定,Bind<.Type1>().To<Type2>();
3)实际使用Ninject,应用Get方法,告诉Get方法用户感兴趣的是哪个接口,即可创建该接口绑定的实例。
using System;using System.Collections.Generic;using System.Web.Mvc;using EssentialTools2.Models;using Ninject;namespace EssentialTools2.Infrastructure{/// <summary>/// 依赖注入器类:MVC框架在需要一个类实例以便对一个传入的请求进行服务时,会调用GetService或GetServices方法/// 依赖解析器要做的工作便是创建这个实例:这一项需要调用Ninject的TryGet和GetAll方法来完成/// </summary>public class NinjectDependencyResolver:IDependencyResolver{private IKernel kernel;public NinjectDependencyResolver() {kernel = new StandardKernel();AddBindings();}public object GetService(Type serviceType) {//TryGet方法所使用的类型参数告诉Ninject,用户感兴趣的是哪个接口,当没有合适的绑定时,会返回null,而不是抛出一个异常return kernel.TryGet(serviceType);}public IEnumerable<object> GetServices(Type serviceType) {//GetAll方法支持对单一类型的多个绑定,当有多个不同的服务提供器时,可用它return kernel.GetAll(serviceType);}private void AddBindings() {kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();}}}
3.注册依赖解析器
必须告诉MVC框架,用户希望使用自己的依赖解析器,通过修改Global.asax.cs文件来完成。
using System.Web.Http;using System.Web.Mvc;using System.Web.Routing;using EssentialTools2.Infrastructure;namespace EssentialTools2{public class MvcApplication : System.Web.HttpApplication{protected void Application_Start(){AreaRegistration.RegisterAllAreas();//使用自己的依赖解析器DependencyResolver.SetResolver(new NinjectDependencyResolver());WebApiConfig.Register(GlobalConfiguration.Configuration);FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);RouteConfig.RegisterRoutes(RouteTable.Routes);}}}
通过这里的添加语句,能让Ninject来创建MVC框架所需的任何对象实例,这便将DI放到了这一应用程序的内核中。
4.重构Home控制器
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using EssentialTools2.Models;namespace EssentialTools2.Controllers{/// <summary>/// 控制器:要先实例化计算器类,再把计算器类作参数带入购物车类的构造器中,然后调用购物车类的方法/// </summary>public class HomeController : Controller{private Product[] products={new Product{Name="Kayak",Category="Watersports",Price=275m},new Product{Name="Lifejacket",Category="Watersports",Price=48.95m},new Product{Name="Soccer ball",Category="Soccer",Price=19.50m},new Product{Name="Corner flag",Category="Soccer",Price=34.95m}};private IValueCalculator calc;//添加一个构造器,接受IValueCalculator接口的实现public HomeController(IValueCalculator calcParam) {calc=calcParam;}public ActionResult Index(){ShoppingCart shoppingCart = new ShoppingCart(calc) {Products=products};decimal result = shoppingCart.CalculateProductTotal();return View(result);}}}
创建依赖性链
当要求Ninject创建一个类型时,它会检查该类与其他类之间的耦合。如果有额外依赖,Ninject会自动解析这些依赖,并创建所有类的实例。
在Models文件夹中添加一个Discount.cs的文件,如下:
namespace EssentialTools2.Models{public interface IDiscountHelper{decimal ApplyDiscount(decimal totalParam);}public class DefaultDiscountHelper : IDiscountHelper {public decimal ApplyDiscount(decimal totalParam) {return (totalParam-(10m/100m*totalParam));}}}
DefaultDiscountHelper类实现了接口,并运用了固定的10%的折扣,此时修改LinqValueCalculator类,使其计算时使用该接口
/// <summary>/// 计算器类:继承接口/// </summary>public class LinqValueCalculator:IValueCalculator{private IDiscountHelper discounter;public LinqValueCalculator(IDiscountHelper discountParam) {discounter = discountParam;}public decimal ValueProducts(IEnumerable<Product> products) {return discounter.ApplyDiscount(products.Sum(x=>x.Price));}}
再在自定义的依赖解析器里增加绑定
private void AddBindings() {kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>();}
指定属性与构造器参数值
在把接口绑定到它的实现时,可以提供想要运用到属性上的一些属性细节,以便对Ninject创建的类进行配置。修改DefaultDiscountHelper类,以使它定义一个DiscountSize属性,该属性用于计算折扣量。
public class DefaultDiscountHelper : IDiscountHelper {public decimal DiscountSize { get; set; }public decimal ApplyDiscount(decimal totalParam) {return (totalParam-(DiscountSize/100m*totalParam));}}
修改Ninject的绑定方法
private void AddBindings() {kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50m);}
结果:

还可以使用构造器来注入
public class DefaultDiscountHelper : IDiscountHelper {//public decimal DiscountSize { get; set; }private decimal discountSize;public DefaultDiscountHelper(decimal discountParam) {discountSize = discountParam;}public decimal ApplyDiscount(decimal totalParam) {return (totalParam-(discountSize/100m*totalParam));}}
只不过Ninject的绑定也要做想要的修改,**注意:这里传的是构造器的参数**
private void AddBindings() {kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();//kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50m);kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam ", 50m);}
使用条件绑定
如果一个接口有多个不同的实现类,比如再多创建一个可以实现不同折扣的类
/// <summary>/// 灵活折扣类/// </summary>public class FlexibleDiscountHelper:IDiscountHelper{public decimal ApplyDiscount(decimal totalParam) {decimal discount = totalParam > 100 ? 70 : 25;return (totalParam-(discount/100m*totalParam));}}
这时就要修改Ninject的绑定方法,告诉Ninject何时使用哪个类来实例化接口。
private void AddBindings() {kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();//kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50m);kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam", 50m);kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedInto<LinqValueCalculator>();}
本例在适当的位置留下对IDiscountHelper的原有绑定,Ninject会尝试找出最佳匹配,而且这有助于对同一个类或接口采用一个默认绑定,以便在条件判断不能得到满足时,让Ninject能够进行回滚。
参考资料
《精通ASP.NET MVC4》
项目代码下载
附件列表

浙公网安备 33010602011771号