九: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():未指定作用域效果如何,为每一个被解析的依赖项创建一个对象(每个依赖项一个实例)
posted @ 2018-11-01 11:40  岚山夜话  阅读(174)  评论(0编辑  收藏  举报