如果大家研究一些开源项目,会发现无处不在的 DI(Dependency Injection 依赖注入)。
本篇文章将会详细讲述如何在 MVC 中使用 Ninject 实现 DI
DI 是一种实现组件解耦的设计模式。
先模拟一个场景来引出问题,我们直接使用 Ninject 官网的示例:一群勇士为了荣耀而战。
首先,我们需要一件合适的武器装备这些勇士。
- class Sword
- {
- public void Hit(string target)
- {
- Console.WriteLine("Chopped {0} clean in half", target);
- }
- }
其次,我们定义勇士类。
勇士有一个 Attack() 方法,用来攻击敌人。
- class Samurai
- {
- readonly Sword sword;
- public Samurai()
- {
- this.sword = new Sword();
- }
- public void Attack(string target)
- {
- this.sword.Hit(target);
- }
- }
现在我们就可以创建一个勇士来战斗。
- class Program
- {
- public static void Main()
- {
- var warrior = new Samurai();
- warrior.Attack("the evildoers");
- }
- }
我们运行这个程序就会打印出 Chopped the evildoers clean in half
现在引出我们的问题:如果我们想要给 Samurai 装备不同的武器呢?
由于 Sword 是在 Samurai 类的构造函数中创建的,必须要改 Samurai 才行。
很显然 Samurai 和 Sword 的耦合性太高了,我们先定义一个接口来解耦。
首先需要建立松耦合组件:通过引入 IWeapon,保证了 Program 与 Sword 之间没有直接的依赖项。
- interface IWeapon
- {
- void Hit(string target);
- }
修改 Sword 类
- class Sword : IWeapon
- {
- public void Hit(string target)
- {
- Console.WriteLine("Chopped {0} clean in half", target);
- }
- }
修改 Samurai 类,将原来构造函数中的 Sword 移到构造函数的参数上,以接口来代替 , 然后我们就可以通过 Samurai 的构造函数来注入 Sword ,这就是一个 DI 的例子(通过构造函数注入)。
- class Samurai
- {
- readonly IWeapon weapon;
- public Samurai(IWeapon weapon)
- {
- this.weapon = weapon;
- }
- public void Attack(string target)
- {
- this.weapon.Hit(target);
- }
- }
如果我们需要用其他武器就不需要修改 Samurai 了。我们再创建另外一种武器。
- class Shuriken : IWeapon
- {
- public void Hit(string target)
- {
- Console.WriteLine("Pierced {0}'s armor", target);
- }
- }
现在我们可以创建装备不同武器的战士了
- class Program
- {
- public static void Main()
- {
- var warrior1 = new Samurai(new Shuriken());
- var warrior2 = new Samurai(new Sword());
- warrior1.Attack("the evildoers");
- warrior2.Attack("the evildoers");
- }
- }
打印出如下结果:
- IWeapon weapon = new Sword();
- var warrior = new Samurai(weapon);
这实际上是将依赖项往后移了,实例化时还是需要对 Program 中进行修改,这破坏了无须修改 Program 就能替换武器的目的。
我们需要达到的效果是,能够获取实现某接口的对象,而又不必直接创建该对象,即 自动依赖项注入。
解决办法是使用 Dependency Injection Container, DI 容器。
以上面的例子来说,它在类(Program)所声明的依赖项和用来解决这些依赖项的类(Sword)之间充当中间件的角色。
可以用 DI 容器注册一组应用程序要使用的接口或抽象类型,并指明满足依赖项所需实例化的实现类。因此在上例中,便会用 DI 容器注册 IWeapon 接口,并指明在需要实现 IWeapon 时,应该创建一个 Sword 的实例。DI 容器会将这两项信息结合在一起,从而创建 Sword 对象,然后用它作为创建 Program 的一个参数,于是在应用程序中便可以使用这个 Sword 了。
接下来,我们就演示下如何使用 Ninject 这个 DI 容器。
为方便在 MVC 中测试,我们对前面的类稍作调整。
Models 文件夹中分别建如下文件:
- namespace XEngine.web.Models
- {
- public interface IWeapon
- {
- string Hit(string target);
- }
- }
- namespace XEngine.Web.Models
- {
- public class Sword:IWeapon
- {
- public string Hit(string target)
- {
- return string.Format("Chopped {0} clean in half", target);
- }
- }
- }
- namespace XEngine.Web.Models
- {
- public class Shuriken:IWeapon
- {
- public string Hit(string target)
- {
- return string.Format("Pierced {0}'s armor", target);
- }
- }
- }
- namespace XEngine.Web.Models
- {
- public class Samurai
- {
- readonly IWeapon weapon;
- public Samurai(IWeapon weapon)
- {
- this.weapon = weapon;
- }
- public string Attack(string target)
- {
- return this.weapon.Hit(target);
- }
- }
- }
测试的 HomeController.cs 文件里增加一个 Action
- public ActionResult Battle()
- {
- var warrior1 = new Samurai(new Sword());
- ViewBag.Res = warrior1.Attack("the evildoers");
- return View();
- }
最后是 Action 对应的 View
- @{
- Layout = null;
- }
- <!DOCTYPE html>
- <html>
- <head>
- <meta name="viewport" content="width=device-width" />
- <title>Battle</title>
- </head>
- <body>
- <div>
- @ViewBag.Res
- </div>
- </body>
- </html>
运行将会看到字符串:Chopped the evildoers clean in half
好了,准备工作都已 OK,下面我们就引入 Ninject
在 VS 中选择 Tools -> Library Package Manager -> Package Manager Console
输入如下命令:
- install-package ninject
- install-package Ninject.Web.Common
运行结果如下:
- PM> install-package ninject
- 正在安装"Ninject 3.2.2.0"。
- 您正在从 Ninject Project Contributors 下载 Ninject,有关此程序包的许可协议在 https://github.com/ninject/ninject/raw/master/LICENSE.txt 上提供。请检查此程序包是否有其他依赖项,这些依赖项可能带有各自的许可协议。您若使用程序包及依赖项,即构成您接受其许可协议。如果您不接受这些许可协议,请从您的设备中删除相关组件。
- 已成功安装"Ninject 3.2.2.0"。
- 正在将"Ninject 3.2.2.0"添加到 XEngine.Web。
- 已成功将"Ninject 3.2.2.0"添加到 XEngine.Web。
- PM> install-package Ninject.Web.Common
- 正在尝试解析依赖项"Ninject (≥ 3.2.0.0 && < 3.3.0.0)"。
- 正在安装"Ninject.Web.Common 3.2.3.0"。
- 您正在从 Ninject Project Contributors 下载 Ninject.Web.Common,有关此程序包的许可协议在 https://github.com/ninject/ninject.extensions.wcf/raw/master/LICENSE.txt 上提供。请检查此程序包是否有其他依赖项,这些依赖项可能带有各自的许可协议。您若使用程序包及依赖项,即构成您接受其许可协议。如果您不接受这些许可协议,请从您的设备中删除相关组件。
- 已成功安装"Ninject.Web.Common 3.2.3.0"。
- 正在将"Ninject.Web.Common 3.2.3.0"添加到 XEngine.Web。
- 已成功将"Ninject.Web.Common 3.2.3.0"添加到 XEngine.Web。
安装完成后就可以使用了,我们修改下 HomeController 中的 Action 方法
基本的功能分三步:
创建内核,配置内核(指定接口和需要绑定类),创建具体对象
具体如下:
- public ActionResult Battle()
- {
- //var warrior1 = new Samurai(new Sword());
- //1. 创建一个Ninject的内核实例
- IKernel ninjectKernel = new StandardKernel();
- //2. 配置Ninject内核,指明接口需绑定的类
- ninjectKernel.Bind<IWeapon>().To<Sword>();
- //3. 根据上一步的配置创建一个对象
- var weapon=ninjectKernel.Get<IWeapon>();
- var warrior1 = new Samurai(weapon);
- ViewBag.Res = warrior1.Attack("the evildoers");
- return View();
- }
查看下 View 中的结果,和一开始一模一样
接口具体需要实例化的类是通过 Get 来获取的,根据字面意思,代码应该很容易理解,我就不多做解释了。
我们完成了使用 Ninject 改造的第一步,不过目前接口和实现类绑定仍是在 HomeController 中定义的,下面我们再进行一轮重构,在 HomeController 中去掉这些配置。
通过创建、注册依赖项解析器达到自动依赖项注入。
这里的依赖项解析器所做的工作就是之前 Ninject 基本功能的三个步骤: 创建内核,配置内核(指定接口和绑定类),创建具体对象。我们通过实现 System.Mvc 命名空间下的 IDependencyResolver 接口来实现依赖项解析器。
待实现的接口:
- namespace System.Web.Mvc
- {
- // 摘要:
- // 定义可简化服务位置和依赖关系解析的方法。
- public interface IDependencyResolver
- {
- // 摘要:
- // 解析支持任意对象创建的一次注册的服务。
- //
- // 参数:
- // serviceType:
- // 所请求的服务或对象的类型。
- //
- // 返回结果:
- // 请求的服务或对象。
- object GetService(Type serviceType);
- //
- // 摘要:
- // 解析多次注册的服务。
- //
- // 参数:
- // serviceType:
- // 所请求的服务的类型。
- //
- // 返回结果:
- // 请求的服务。
- IEnumerable<object> GetServices(Type serviceType);
- }
- }
具体实现:
- namespace XEngine.Web.Infrastructure
- {
- public class NinjectDependencyResolver:IDependencyResolver
- {
- private IKernel kernel;
- public NinjectDependencyResolver(IKernel kernelParam)
- {
- kernel = kernelParam;
- AddBindings();
- }
- public object GetService(Type serviceType)
- {
- return kernel.TryGet(serviceType);
- }
- public IEnumerable<object> GetServices(Type serviceType)
- {
- return kernel.GetAll(serviceType);
- }
- private void AddBindings()
- {
- kernel.Bind<IWeapon>().To<Sword>();
- }
- }
- }
MVC 框架在需要类实例以便对一个传入的请求进行服务时,会调用 GetService 或 GetServices 方法。依赖项解析器要做的工作便是创建这一实例。
还剩最后一步,注册依赖项解析器。
再次打开 Package Manager Console
输入如下命令:
- install-package Ninject.MVC5
运行结果
- PM> install-package Ninject.MVC5
- 正在尝试解析依赖项"Ninject (≥ 3.2.0.0 && < 3.3.0.0)"。
- 正在尝试解析依赖项"Ninject.Web.Common.WebHost (≥ 3.0.0.0)"。
- 正在尝试解析依赖项"Ninject.Web.Common (≥ 3.2.0.0 && < 3.3.0.0)"。
- 正在尝试解析依赖项"WebActivatorEx (≥ 2.0 && < 3.0)"。
- 正在尝试解析依赖项"Microsoft.Web.Infrastructure (≥ 1.0.0.0)"。
- 正在安装"WebActivatorEx 2.0"。
- 已成功安装"WebActivatorEx 2.0"。
- 正在安装"Ninject.Web.Common.WebHost 3.2.0.0"。
- 您正在从 Ninject Project Contributors 下载 Ninject.Web.Common.WebHost,有关此程序包的许可协议在 https://github.com/ninject/ninject.web.common/raw/master/LICENSE.txt 上提供。请检查此程序包是否有其他依赖项,这些依赖项可能带有各自的许可协议。您若使用程序包及依赖项,即构成您接受其许可协议。如果您不接受这些许可协议,请从您的设备中删除相关组件。
- 已成功安装"Ninject.Web.Common.WebHost 3.2.0.0"。
- 正在安装"Ninject.MVC5 3.2.1.0"。
- 您正在从 Remo Gloor, Ian Davis 下载 Ninject.MVC5,有关此程序包的许可协议在 https://github.com/ninject/ninject.web.mvc/raw/master/mvc3/LICENSE.txt 上提供。请检查此程序包是否有其他依赖项,这些依赖项可能带有各自的许可协议。您若使用程序包及依赖项,即构成您接受其许可协议。如果您不接受这些许可协议,请从您的设备中删除相关组件。
- 已成功安装"Ninject.MVC5 3.2.1.0"。
- 正在将"WebActivatorEx 2.0"添加到 XEngine.Web。
- 已成功将"WebActivatorEx 2.0"添加到 XEngine.Web。
- 正在将"Ninject.Web.Common.WebHost 3.2.0.0"添加到 XEngine.Web。
- 已成功将"Ninject.Web.Common.WebHost 3.2.0.0"添加到 XEngine.Web。
- 正在将"Ninject.MVC5 3.2.1.0"添加到 XEngine.Web。
- 已成功将"Ninject.MVC5 3.2.1.0"添加到 XEngine.Web。
可以看到 App_Start 文件夹下多了一个 NinjectWebCommon.cs 文件,它定义了应用程序启动时会自动调用的一些方法,将它们集成到 ASP.NET 的请求生命周期之中。
找到最后一个方法 RegisterServices,只需要添加一句即可。
- public static class NinjectWebCommon
- {
- /// <summary>
- /// Load your modules or register your services here!
- /// </summary>
- /// <param name="kernel">The kernel.</param>
- private static void RegisterServices(IKernel kernel)
- {
- System.Web.Mvc.DependencyResolver.SetResolver(new XEngine.Web.Infrastructure.NinjectDependencyResolver(kernel));
- }
- }
主要添加一个构造函数来接收接口的实现,如下
- private IWeapon weapon;
- public HomeController(IWeapon weaponParam)
- {
- weapon = weaponParam;
- }
- public ActionResult Battle()
- {
- //var warrior1 = new Samurai(new Sword());
- ////1. 创建一个Ninject的内核实例
- //IKernel ninjectKernel = new StandardKernel();
- ////2. 配置Ninject内核,指明接口需绑定的类
- //ninjectKernel.Bind<IWeapon>().To<Sword>();
- ////3. 根据上一步的配置创建一个对象
- //var weapon=ninjectKernel.Get<IWeapon>();
- var warrior1 = new Samurai(weapon);
- ViewBag.Res = warrior1.Attack("the evildoers");
- return View();
- }
运行可以看到和之前一样的效果。
这种依赖项是在运行中才被注入到 HomeController 中的,这就是说,在类的实例化期间才会创建 IWeapon 接口的实现类实例,并将其传递给 HomeController 构造器。HomeController 与依赖项接口的实现类直接不存在编译时的依赖项。
我们完全可以用另一个武器而无需对 HomeController 做任何修改。
DI 是一种实现组件解耦的设计模式。分成两个步骤:
依赖项注入除了通过构造函数的方式还可以通过属性注入和方法注入,展开讲还有很多东西,我们还是按照一贯的风格,够用就好,先带大家扫清障碍,大家先直接模仿着实现就好了。
进一步学习可以参考官网学习教程:
后续文章项目实战部分,会根据项目实际需求,用到时再展开讲。
祝学习进步:)
来源: http://www.cnblogs.com/miro/p/6671503.html