MVC 5 + EF6 完整教程15 -- 使用DI进行解耦

若是你们研究一些开源项目,会发现无处不在的DI(Dependency Injection依赖注入)。
本篇文章将会详细讲述如何在MVC中使用Ninject实现DIhtml

文章提纲

  • 场景描述 & 问题引出
  • 第一轮重构
  • 引入Ninject
  • 第二轮重构
  • 总结

场景描述 & 问题引出

DI是一种实现组件解耦的设计模式。
先模拟一个场景来引出问题,咱们直接使用Ninject官网的示例:一群勇士为了荣耀而战。
首先,咱们须要一件合适的武器装备这些勇士。git

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

其次,咱们定义勇士类。
勇士有一个Attack()方法,用来攻击敌人。github

class Samurai
{
    readonly Sword sword;
    public Samurai()
    {
        this.sword = new Sword();
    }
    
    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

如今咱们就能够建立一个勇士来战斗。web

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之间没有直接的依赖项。mvc

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");
    }
}

打印出以下结果:

Pierced the evildoers armor.
Chopped the evildoers clean in half.
至此已解决了依赖项问题,以上的作法咱们称为手工依赖注入。
每次须要建立一个 Samurai时都必须首先创造一个 IWeapon接口的实现,而后传递到 Samurai的构造函数中。
但如何对接口的具体实现进行实例化而无须在应用程序的某个地方建立依赖项呢? 按照如今的状况,在应用程序的某个地方仍然须要如下这些语句。

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容器。

引入Ninject

为方便在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

1、将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方法

2、使用Ninject完成绑定功能

基本的功能分三步:
建立内核,配置内核(指定接口和须要绑定类),建立具体对象
具体以下:

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中去掉这些配置。

第二轮重构

经过建立、注册依赖项解析器达到自动依赖项注入。

1、建立依赖项解析器

这里的依赖项解析器所作的工做就是以前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方法。依赖项解析器要作的工做即是建立这一实例。

2、注册依赖项解析器

还剩最后一步,注册依赖项解析器。
再次打开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));
    }        
}

3、重构HomeController

主要添加一个构造函数来接收接口的实现,以下

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是一种实现组件解耦的设计模式。分红两个步骤:

  1. 打断和声明依赖项
    建立一个类构造函数,以所需接口的实现做为其参数,去除对具体类的依赖项。
  2. 注射依赖项
    经过建立、注册依赖项解析器达到自动依赖项注入。

依赖项注入除了经过构造函数的方式还能够经过属性注入和方法注入,展开讲还有不少东西,咱们仍是按照一向的风格,够用就好,先带你们扫清障碍,你们先直接模仿着实现就行了。
进一步学习能够参考官网学习教程:https://github.com/ninject/Ninject/wiki
后续文章项目实战部分,会根据项目实际需求,用到时再展开讲。

祝学习进步:)

相关文章
相关标签/搜索