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
後續文章項目實戰部分,會根據項目實際需求,用到時再展開講。

祝學習進步:)

相關文章
相關標籤/搜索