mvc 控制反转Ninject(依赖注入容器)

来源:互联网 发布:如何修改电脑网络通道 编辑:程序博客网 时间:2024/05/17 06:05

前面也有说"控制反转"所谓的依赖注入(Dependency Injection)简称DI。针对它有一款不错的容器,那就是"Ninject",这东西是一个简单易用的东西。话不多说,直接开始吧!

使用Ninject

先用控制台程序玩玩首先定义一个类,接口以及它的实现,代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class Product : Object { 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; } } }
复制代码

下面创建一个接口,具体代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
 using System.Text;
 namespace Ninject_Tools
 {   public interface IValueCalculator 
    {
      decimal ValueProducts(params Product[] products);
    }
 }

注:上面的IValueCalculator里面多了一个ValueProducts的方法,用于返回商品的累计值。

接着我们写一个类来实现接口IValueCalculator,代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class LinqValueCalculator : IValueCalculator { public decimal ValueProducts(params Product[] products) { return products.Sum(h => h.Price); } } }
复制代码

注:这里用LINQ的扩张方法来计算Prducts商品的总价,当然还有其他的办法也可以实现。

接下来我们需要创建一个类来实现依赖注入,具体的代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class ShoppingCart : Object { private IValueCalculator calcuator; public ShoppingCart(IValueCalculator calcParam) { this.calcuator = calcParam; } public decimal CalculateStockValue() { //计算产品集总和 Product[] products = { new Product(){Name="Huitai",Price=1M}, new Product(){Name="ShuaiShuai",Price=10M}, new Product(){Name="Jack",Price=100M}, new Product(){Name="Cena",Price=200M} }; //计算产品总和 decimal totaValue = this.calcuator.ValueProducts(products); //返回结果 return totaValue; } } }
复制代码

注:ShoppingCart构造器接受接口IValueCalculator的实现的实例作为参数来构造注入来实现ShoppingCart与LinqValueCalculator(IValueCalculator接口实现)的解耦,这也就是依赖注入里的"构造注入(Constructor Injection)".它们之间的关系如下图1.

图1.

由上面的图可以看出ShoppingCart类和LinqValueCalculator类都依赖于IValueCalcutor,ShoppingCart和LinqValueCalculator没有直接关系,甚至都不知道他的存在。

下面我们就需要添加Ninject到我们的项目里来玩玩,在我们的应用程序里使用管理程序"NuGet"包来引入Ninject的DLL,如下图2.给我们的应用程序来加载进来Ninject.dll.

图2.

加载进来Ninject.dll,然后我们就需要开始使用它了。

然后我们在控制台程序的Mian方法里使用它,具体的代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //创建Ninject实例,方便我们下面使用 IKernel ninjectKerenl = new StandardKernel(); //绑定相关的类型和创建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); //得到接口的实现 IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); //创建实例ShoppingCart实例依赖注入 ShoppingCart cart = new ShoppingCart(calcImpl); //输出 Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
复制代码

好了,现在跑一下我们的程序,可以看到如下图3.的结果。

图3.

小结一下吧!我们开始解耦,然后将IValueCalcutor的实例对象作为参数传递给ShoppingCart的构造器的方式,然后我们通过"依赖注入"容器来处理这个参数,在上面我们用Ninject来实现的,到这里相信大家对NinJect有一点初步的认识。

 创建依赖链(Creating Chains of Dependency)

当向Ninject创建一个类型,它检查该类型与其它类型之间的耦合关系,类型和其他类型。如果有额外的依赖,Ninject解决它们并创建新的我们需要的类的实例。

给我们的控制台程序继续添加一个新的接口IDiscountHelper进来,代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public interface IDiscountHelper { decimal ApplyDiscount(decimal totalParam); } }
复制代码

然后在添加一个实现新接口IDiscountHelper的类DefaultDiscountHelper进来,代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class DefaultDiscountHelper : IDiscountHelper { public decimal ApplyDiscount(decimal totalParam) { return (totalParam - (10m / 100m * totalParam)); } } }
复制代码

然后在我们上面的LinqValueCalculator类里添加依赖,代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class LinqValueCalculator : IValueCalculator { private IDiscountHelper discounter; //LinqValueCalculator构造器接受IDiscountHelper实例对象 public LinqValueCalculator(IDiscountHelper discountParam) { this.discounter = discountParam; } public decimal ValueProducts(params Product[] products) { return this.discounter.ApplyDiscount(products.Sum(h => h.Price)); } } }
复制代码

然后就是绑定实现接口,具体代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //创建Ninject实例,方便我们下面使用 IKernel ninjectKerenl = new StandardKernel(); //绑定相关的类型和创建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); //得到接口的实现 IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); //创建实例ShoppingCart实例依赖注入 ShoppingCart cart = new ShoppingCart(calcImpl); //输出 Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
复制代码

然后在跑下我们修改后的程序,结果如下图4.

图4.

小结一下吧:代码中Ninject将两个接口分别绑定到对应的实现类中,我们没有改变实现IValueCalculator类的代码。当我们请求IValueCalculator的类型时Ninject知道就实例化LinqValueCalculator对象来返回。但是它会去检查这个类,发现LinqValueCalculator还同时依赖另一个接口,并且这个接口是它能解析的,Ninject创建一个DefaultDiscountHelper的实例注入LinqValueCalculator类的构造器,并返回IValueCalculator类型的对象。Ninject会只用这种方式检查每一个要实例化的类的依赖,不管依赖链有多的复杂。

指定属性和参数值(Specifying Property and Parameter Values)

我们可以配置类提供一些属性或者具体的值传给Ninject,当我们绑定和接口的实现。然后我们对上面的DefaultDiscountHelper类写死的折扣经行修改。

然后我们给DefaultDiscountHelper类添加一条属性,代码如下:

 

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class DefaultDiscountHelper : IDiscountHelper { public decimal DiscountSize { get; set; } public decimal ApplyDiscount(decimal totalParam) { return (totalParam - (this.DiscountSize / 100m * totalParam)); } } }
复制代码

 

然后在我们绑定实现接口的时候,就可以动态的使用WithPropertyValue来动态的设置我们的折扣力度。代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //创建Ninject实例,方便我们下面使用 IKernel ninjectKerenl = new StandardKernel(); //绑定相关的类型和创建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50M); //得到接口的实现 IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); //创建实例ShoppingCart实例依赖注入 ShoppingCart cart = new ShoppingCart(calcImpl); //输出 Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
复制代码

添加了属性的项目跑起来看是不是我们输入5折的折扣,运行起来如下图5.

图5.

使用自动绑定(Self-Binding)

一个有用的功能整合到你的代码中完全是Ninject自我绑定,也就是来自Ninject内核被请求的类的地方。这似乎是一个奇怪的事情,但是这意味着我们不用手动的执行DI初始化,空值太程序可以修改如下代码所示:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //创建Ninject实例,方便我们下面使用 IKernel ninjectKerenl = new StandardKernel(); //绑定相关的类型和创建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M); ////得到接口的实现 //IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); ////创建实例ShoppingCart实例依赖注入 //ShoppingCart cart = new ShoppingCart(calcImpl); ////输出 //这里是调用ShoppingCart本身,所以注释掉这行代码程序也可以执行,但是调用具体的类经行自我绑定,代码如下: //ninjectKernel.Bind<ShoppingCart>().ToSelf().WithParameter("<parameterName>", <paramvalue>); ninjectKerenl.Bind<ShoppingCart>().ToSelf(); ShoppingCart cart = ninjectKerenl.Get<ShoppingCart>(); Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
复制代码

运行程序可以看到如下图6.所示的结果

图6.

绑定派生类型(Binding to a Derived Type)

 前面一直都在接口绑定的方面(因为接口在MVC里相关性更强),我们也可以使用Ninject绑定具体类。Self-Binding就是这样搞的。那样我们可以使用Ninject绑定一个具体的类到它的派生类,因为前面我们绑定接口到实现它的具体类--实现类继承了该接口,这里就是类继承类罢了。那我们修改一下ShoppingCart类,代码如下:

 

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class ShoppingCart : Object { protected IValueCalculator calcuator; protected Product[] products; public ShoppingCart(IValueCalculator calcParam) { this.calcuator = calcParam; //计算产品集总和 Product[] products = { new Product(){Name="Huitai",Price=1M}, new Product(){Name="ShuaiShuai",Price=10M}, new Product(){Name="Jack",Price=100M}, new Product(){Name="Cena",Price=200M} }; } public virtual decimal CalculateStockValue() { //计算产品总和 decimal totaValue = this.calcuator.ValueProducts(products); //返回结果 return totaValue; } } }
复制代码

写一个LimitShoppingCart类派生自ShoppingCart类:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ninject_Tools { public class LimitShoppingCart : ShoppingCart { public LimitShoppingCart(IValueCalculator calcParam) : base(calcParam) { } public override decimal CalculateStockValue() { //过滤掉价格超过我们设定价格的商品然后在求和 var filteredProducts = products.Where(h => h.Price < ItemLimit); return this.calcuator.ValueProducts(filteredProducts.ToArray()); } public decimal ItemLimit { get; set; } } }
复制代码

 然后绑定ShoppingCart到它的派生类LimitShoppingCart类,代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Ninject; namespace Ninject_Tools { class Program { static void Main(string[] args) { //创建Ninject实例,方便我们下面使用 IKernel ninjectKerenl = new StandardKernel(); //绑定相关的类型和创建的接口 ninjectKerenl.Bind<IValueCalculator>().To<LinqValueCalculator>(); ninjectKerenl.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M); ////得到接口的实现 //IValueCalculator calcImpl = ninjectKerenl.Get<IValueCalculator>(); ////创建实例ShoppingCart实例依赖注入 //ShoppingCart cart = new ShoppingCart(calcImpl); ////输出 //这里是调用ShoppingCart本身,所以注释掉这行代码程序也可以执行,但是调用具体的类经行自我绑定,代码如下: //ninjectKernel.Bind<ShoppingCart>().ToSelf().WithParameter("<parameterName>", <paramvalue>); //ninjectKerenl.Bind<ShoppingCart>().ToSelf();  ninjectKerenl.Bind<ShoppingCart>().To<LimitShoppingCart>().WithPropertyValue("ItemLimit", 100M); ShoppingCart cart = ninjectKerenl.Get<ShoppingCart>(); Console.WriteLine("Total:{0:C}", cart.CalculateStockValue()); } } }
复制代码

使用条件绑定(Using Conditional Binding)

我们可对同一个接口会有多重实现或者是对同一个类有多个派生,这时Ninject可以指定不同的条件来说明哪一个应该被使用。比如下面,我们创建一个新的类IterativeValueCalculator实现IValueCalculator,代码如下:

 

View Code

 

下面是对IValueCalculator的两个不同实现了,可以通过Ninject设置条件来指定哪个显示被应用。

View Code

从上面的代码可以看出,绑定指定IterativeValueCalculator类应该被实例化在绑定IValueCalculatorinterface时被当对象的依赖注入。如果我们的条件没有一个合适也没有关系,Ninject会寻找一个对该类或接口的默认的绑定,以至于Ninject会有一个返回的结果。

在Asp.Net MVC 中使用Ninject及运用单元测试

前面是都是控制台程序里面玩Ninject,在MVC玩Ninject可就没那么简单了,比起来就稍微有点复杂了。开始是我们需要创建一个类,它的派生自System.Web.Mvc.DefaultControllerFactory。该类MVC依赖默认情况下创建控制器类的实例。.Net有好多单元测试包,其中又由好多包开放源代码,但愿测试工具最受欢迎那可能就是NUnit。我们就玩玩怎么创建但愿测试和填充测试。

首先先看看在Asp.Net MVC 中如何使用Ninject,开始需要创建一个类,让他继承DefaultControllerFactory ,代码如下:

View Code

这个类创建一个Ninject内核,通过GetControllerInstance方法为控制器类的请求服务,这个方法在需要一个控制器对象的时候被MVC框架调用。我们不需要显式地使用Ninject绑定控制器类。我们可以依靠默认的self-binding(自我绑定)特性,因为controllersare具体类派生自 System.Web.Mvc.Controller。AddBindings()方法允许我们绑定Repositories和其他需要保持松耦合的组件。我们也可以使用这个方法绑定需要额外的构造器参数或属性参数的controller classes。

一旦我们创建这个类,我们必须注册它与MVC框架,我们需要在Global.asax文件测试它,具体代码如下:

复制代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using MVC_Tools.Models; namespace MVC_Tools { // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, // 请访问 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值 ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); } } }
复制代码

现在MVC框架将使用我们的NinjectControllerFactoryto获得实例的控制器类,Ninject会处理迪向控制器对象自动。有关这东西就先了解到这里吧!

下面看我们的单元测试,弄一个控制台程序来搞吧!

创建相关的Product类,IPriceReducer,IProductRepository接口如下面:

View Code
View Code
View Code

IProductRepository定义了一个存储库,我们通过将获取和更新Product对象。IPriceReducer定义了一个具体的降价方法,针对所有的Products。我们的目标就是实现这些接口并需要遵循下面的情况:

  • 所有的Product都应该降价
  • 总共的价格必须等于所有的Product数量乘以降价数额的乘积
  • 降价后的Products必须大于1美元
  • Repository的UpdateProduct方法应该被每一个Product商品调用到

然后我们添加一个实现接口IProductRepository的类FakeRepository,代码如下:

View Code

还需要添加一个类MyPriceReducer去实现IPriceReducer接口,代码如下:

View Code

开始我们的单元测试吧!我们要按照TDD模式和编写单元测试代码之前编写应用程序,点击MyPriceReducer类里的ReducePrices方法,然后选择"创建单元测试",如下图6.

图6.当选择"创建单元测试"会出现如下图7.所示的弹出框,

图7.由于单元测试是一个单独的项目,所以我们给他取名"Test_Project",,创建完成后会新建,一个单元测试的项目加载进来,并生成了一个测试类MyPriceReducerTest,它里面包含一些属性和方法让供我们使用,具体生成代码如下:

View Code

 但是对我们来说最重要莫过于项目了,所以我们将代码修改成下面的形式:

View Code

运行单元测试的的代码,结果如下图8.所示

图8.因为我们没有去实现ReducePrices,单元测试不能通过。有很多不同的方式来构建单元测试年代。常见的是有一个方法是测试所需的所有功能的条件。但是我们更喜欢创建很多小的单元测试,每个单元测试只关注应用程序的一个方面。当让给单元测试方法名字的命名规则根据自己的编程风格自己定义,只要符合命名规范即可。把测试的全部功能分散到一个一个的小的测试方法里经行,通过这种方式不断的完善我们的驱动测试开发(TDD).如下面的代码:

View Code

然后运行我们的单元测试代码,结果如图9.

图9.其实上面的单元的测试代码里也使用了简单依赖注入(构造器注入),上面的每一个测试方法就是针对一个功能来测试,貌似测试没有通过,那是因为我们的ReducePrices方法没有实现,现在我们就是去搞下他吧!代码具体如下:

View Code

搞完之后,在来跑下我们的单元测试的模块,运行结果如下图10.

图10.呵呵!其实这些基本都是VS里面的东西,差不多随便看看点点,写写就会明白的吧!好了就先分享这么一点东西吧!大家共同学习进步,文章里那里要是有错误还是那里描述错误的,请路过的朋友们,前辈们多多指导,批评。这样我们才能更好的学习。

原创粉丝点击