zoukankan      html  css  js  c++  java
  • MVC开发之注入容器Ninject的使用

    背景


    在不使用注入容器之前,我们的项目往往存在着大量耦合的类,这使得我们在开发大型项目时难以维护。比如下面这个简单的MVC框架的例子,计算购物车的产品价格总和:
    1. /// <summary>
    2. /// 产品模型类
    3. /// </summary>
    4. public class Product
    5. {
    6. public int ProductID { get; set; }
    7. public string Name { get; set; }
    8. public string Description { get; set; }
    9. public decimal Price { get; set; }
    10. public string Category { get; set; }
    11. }
    12. /// <summary>
    13. /// 计算器类:使用LINQ的sum方法来计算产品的总价
    14. /// </summary>
    15. public class LinqValueCalculator
    16. {
    17. public decimal ValueProducts(IEnumerable<Product> products) {
    18. return products.Sum(x=>x.Price);
    19. }
    20. }
    21. /// <summary>
    22. /// 购物车类:表示Product对象的集合,并使用计算器类LinqValueCalculator来计算总价【Product集合作自动属性,利用构造器初始化计算器类,再定义一个方法,里面调用计算器类的方法】
    23. /// </summary>
    24. public class ShoppingCart
    25. {
    26. private LinqValueCalculator calc;
    27. public IEnumerable<Product> Products { get; set; }
    28. public ShoppingCart(LinqValueCalculator calc) {
    29. this.calc = calc;
    30. }
    31. public decimal CalculateProductTotal() {
    32. return calc.ValueProducts(Products);
    33. }
    34. }
    35. /// <summary>
    36. /// 控制器:要先实例化计算器类,再把计算器类作参数带入购物车类的构造器中,然后调用购物车类的方法
    37. /// </summary>
    38. public class HomeController : Controller
    39. {
    40. private Product[] products={
    41. new Product{Name="Kayak",Category="Watersports",Price=275m},
    42. new Product{Name="Lifejacket",Category="Watersports",Price=48.95m},
    43. new Product{Name="Soccer ball",Category="Soccer",Price=19.50m},
    44. new Product{Name="Corner flag",Category="Soccer",Price=34.95m}
    45. };
    46. public ActionResult Index()
    47. {
    48. LinqValueCalculator calc = new LinqValueCalculator();
    49. ShoppingCart shoppingCart = new ShoppingCart(calc) {
    50. Products=products
    51. };
    52. decimal result = shoppingCart.CalculateProductTotal();
    53. return View(result);
    54. }
    55. }
    最后view视图呈现的结果:
     在该项目中依赖一些紧耦合的类:ShoppingCart类与LinqValueCalculator类是紧耦合的,而HomeController类与ShoppingCart和LinqValueCalculator都是紧耦合的。这意味着,如果想替换LinqValueCalculator类,就必须在与它有紧耦合关系的类中找到对它的引用,并修改。这对于一个实际项目中,就是一个乏味且易错的过程。

    运用接口


    1. /// <summary>
    2. /// 接口:从计算器的实现中抽象出其功能定义
    3. /// </summary>
    4. public interface IValueCalculator
    5. {
    6. decimal ValueProducts(IEnumerable<Product> products);
    7. }
    8. /// <summary>
    9. /// 计算器类:继承接口
    10. /// </summary>
    11. public class LinqValueCalculator:IValueCalculator
    12. {
    13. public decimal ValueProducts(IEnumerable<Product> products) {
    14. return products.Sum(x=>x.Price);
    15. }
    16. }
    17. /// <summary>
    18. /// 购物车类
    19. /// </summary>
    20. public class ShoppingCart
    21. {
    22. //都改为传递接口,解除ShoppingCart与ValueCalculator之间的耦合
    23. private IValueCalculator calc;
    24. public IEnumerable<Product> Products { get; set; }
    25. public ShoppingCart(IValueCalculator calc)
    26. {
    27. this.calc = calc;
    28. }
    29. public decimal CalculateProductTotal() {
    30. return calc.ValueProducts(Products);
    31. }
    32. }
    33. /// <summary>
    34. /// 控制器:要先实例化计算器类,再把计算器类作参数带入购物车类的构造器中,然后调用购物车类的方法
    35. /// </summary>
    36. public class HomeController : Controller
    37. {
    38. private Product[] products={
    39. new Product{Name="Kayak",Category="Watersports",Price=275m},
    40. new Product{Name="Lifejacket",Category="Watersports",Price=48.95m},
    41. new Product{Name="Soccer ball",Category="Soccer",Price=19.50m},
    42. new Product{Name="Corner flag",Category="Soccer",Price=34.95m}
    43. };
    44. public ActionResult Index()
    45. {
    46. //但是Home控制器和LinqValueCalculator类还是紧耦合关系
    47. IValueCalculator calc = new LinqValueCalculator();
    48. ShoppingCart shoppingCart = new ShoppingCart(calc) {
    49. Products=products
    50. };
    51. decimal result = shoppingCart.CalculateProductTotal();
    52. return View(result);
    53. }
    54. }

    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方法用户感兴趣的是哪个接口,即可创建该接口绑定的实例。
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Web.Mvc;
    4. using EssentialTools2.Models;
    5. using Ninject;
    6. namespace EssentialTools2.Infrastructure
    7. {
    8. /// <summary>
    9. /// 依赖注入器类:MVC框架在需要一个类实例以便对一个传入的请求进行服务时,会调用GetService或GetServices方法
    10. /// 依赖解析器要做的工作便是创建这个实例:这一项需要调用Ninject的TryGet和GetAll方法来完成
    11. /// </summary>
    12. public class NinjectDependencyResolver:IDependencyResolver
    13. {
    14. private IKernel kernel;
    15. public NinjectDependencyResolver() {
    16. kernel = new StandardKernel();
    17. AddBindings();
    18. }
    19. public object GetService(Type serviceType) {
    20. //TryGet方法所使用的类型参数告诉Ninject,用户感兴趣的是哪个接口,当没有合适的绑定时,会返回null,而不是抛出一个异常
    21. return kernel.TryGet(serviceType);
    22. }
    23. public IEnumerable<object> GetServices(Type serviceType) {
    24. //GetAll方法支持对单一类型的多个绑定,当有多个不同的服务提供器时,可用它
    25. return kernel.GetAll(serviceType);
    26. }
    27. private void AddBindings() {
    28. kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
    29. }
    30. }
    31. }

    3.注册依赖解析器

    必须告诉MVC框架,用户希望使用自己的依赖解析器,通过修改Global.asax.cs文件来完成。
    1. using System.Web.Http;
    2. using System.Web.Mvc;
    3. using System.Web.Routing;
    4. using EssentialTools2.Infrastructure;
    5. namespace EssentialTools2
    6. {
    7. public class MvcApplication : System.Web.HttpApplication
    8. {
    9. protected void Application_Start()
    10. {
    11. AreaRegistration.RegisterAllAreas();
    12. //使用自己的依赖解析器
    13. DependencyResolver.SetResolver(new NinjectDependencyResolver());
    14. WebApiConfig.Register(GlobalConfiguration.Configuration);
    15. FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    16. RouteConfig.RegisterRoutes(RouteTable.Routes);
    17. }
    18. }
    19. }
    通过这里的添加语句,能让Ninject来创建MVC框架所需的任何对象实例,这便将DI放到了这一应用程序的内核中。

    4.重构Home控制器

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Web;
    5. using System.Web.Mvc;
    6. using EssentialTools2.Models;
    7. namespace EssentialTools2.Controllers
    8. {
    9. /// <summary>
    10. /// 控制器:要先实例化计算器类,再把计算器类作参数带入购物车类的构造器中,然后调用购物车类的方法
    11. /// </summary>
    12. public class HomeController : Controller
    13. {
    14. private Product[] products={
    15. new Product{Name="Kayak",Category="Watersports",Price=275m},
    16. new Product{Name="Lifejacket",Category="Watersports",Price=48.95m},
    17. new Product{Name="Soccer ball",Category="Soccer",Price=19.50m},
    18. new Product{Name="Corner flag",Category="Soccer",Price=34.95m}
    19. };
    20. private IValueCalculator calc;
    21. //添加一个构造器,接受IValueCalculator接口的实现
    22. public HomeController(IValueCalculator calcParam) {
    23. calc=calcParam;
    24. }
    25. public ActionResult Index()
    26. {
    27. ShoppingCart shoppingCart = new ShoppingCart(calc) {
    28. Products=products
    29. };
    30. decimal result = shoppingCart.CalculateProductTotal();
    31. return View(result);
    32. }
    33. }
    34. }

    创建依赖性链

    当要求Ninject创建一个类型时,它会检查该类与其他类之间的耦合。如果有额外依赖,Ninject会自动解析这些依赖,并创建所有类的实例。
    在Models文件夹中添加一个Discount.cs的文件,如下:
    1. namespace EssentialTools2.Models
    2. {
    3. public interface IDiscountHelper
    4. {
    5. decimal ApplyDiscount(decimal totalParam);
    6. }
    7. public class DefaultDiscountHelper : IDiscountHelper {
    8. public decimal ApplyDiscount(decimal totalParam) {
    9. return (totalParam-(10m/100m*totalParam));
    10. }
    11. }
    12. }

    DefaultDiscountHelper类实现了接口,并运用了固定的10%的折扣,此时修改LinqValueCalculator类,使其计算时使用该接口
    1. /// <summary>
    2. /// 计算器类:继承接口
    3. /// </summary>
    4. public class LinqValueCalculator:IValueCalculator
    5. {
    6. private IDiscountHelper discounter;
    7. public LinqValueCalculator(IDiscountHelper discountParam) {
    8. discounter = discountParam;
    9. }
    10. public decimal ValueProducts(IEnumerable<Product> products) {
    11. return discounter.ApplyDiscount(products.Sum(x=>x.Price));
    12. }
    13. }

    再在自定义的依赖解析器里增加绑定
    1. private void AddBindings() {
    2. kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
    3. kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>();
    4. }

    指定属性与构造器参数值

    在把接口绑定到它的实现时,可以提供想要运用到属性上的一些属性细节,以便对Ninject创建的类进行配置。修改DefaultDiscountHelper类,以使它定义一个DiscountSize属性,该属性用于计算折扣量。
    1. public class DefaultDiscountHelper : IDiscountHelper {
    2. public decimal DiscountSize { get; set; }
    3. public decimal ApplyDiscount(decimal totalParam) {
    4. return (totalParam-(DiscountSize/100m*totalParam));
    5. }
    6. }

    修改Ninject的绑定方法
    1. private void AddBindings() {
    2. kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
    3. kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50m);
    4. }

    结果:
    还可以使用构造器来注入
    1. public class DefaultDiscountHelper : IDiscountHelper {
    2. //public decimal DiscountSize { get; set; }
    3. private decimal discountSize;
    4. public DefaultDiscountHelper(decimal discountParam) {
    5. discountSize = discountParam;
    6. }
    7. public decimal ApplyDiscount(decimal totalParam) {
    8. return (totalParam-(discountSize/100m*totalParam));
    9. }
    10. }

    只不过Ninject的绑定也要做想要的修改,**注意:这里传的是构造器的参数**
    1. private void AddBindings() {
    2. kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
    3. //kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50m);
    4. kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam ", 50m);
    5. }

    使用条件绑定

    如果一个接口有多个不同的实现类,比如再多创建一个可以实现不同折扣的类
    1. /// <summary>
    2. /// 灵活折扣类
    3. /// </summary>
    4. public class FlexibleDiscountHelper:IDiscountHelper
    5. {
    6. public decimal ApplyDiscount(decimal totalParam) {
    7. decimal discount = totalParam > 100 ? 70 : 25;
    8. return (totalParam-(discount/100m*totalParam));
    9. }
    10. }

    这时就要修改Ninject的绑定方法,告诉Ninject何时使用哪个类来实例化接口。
    1. private void AddBindings() {
    2. kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
    3. //kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50m);
    4. kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam", 50m);
    5. kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedInto<LinqValueCalculator>();
    6. }

    本例在适当的位置留下对IDiscountHelper的原有绑定,Ninject会尝试找出最佳匹配,而且这有助于对同一个类或接口采用一个默认绑定,以便在条件判断不能得到满足时,让Ninject能够进行回滚。

    参考资料

    《精通ASP.NET MVC4》

    项目代码下载


    附件列表

    • 相关阅读:
      Java调度实现
      关于《报表》的实际运用案例
      mybaits错误解决:There is no getter for property named 'parentId ' in class 'java.lang.String'
      Java Eclipse进行断点调试
      切割时间工具类
      JavaWeb开发技术基础概念回顾篇
      解决无线网络连接出现黄色感叹号---win10
      登录界面Demo
      MD5加密Demo
      java.lang.NullPointerException&com.cb.action.LoginAction.execute(LoginAction.java:48)
    • 原文地址:https://www.cnblogs.com/iwsx/p/Ninject_Learn.html
    Copyright © 2011-2022 走看看