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

来源:http://www.cnblogs.com/miro/archive/2017/04/06/6671503.html
-Advertisement-
Play Games

如果大家研究一些開源項目,會發現無處不在的DI(Dependency Injection依賴註入)。 本篇文章將會詳細講述如何在MVC中使用Ninject實現DI 文章提綱 場景描述 & 問題引出 第一輪重構 引入Ninject 第二輪重構 總結 場景描述 & 問題引出 DI是一種實現組件解耦的設計 ...


如果大家研究一些開源項目,會發現無處不在的DI(Dependency Injection依賴註入)。
本篇文章將會詳細講述如何在MVC中使用Ninject實現DI

文章提綱

  • 場景描述 & 問題引出
  • 第一輪重構
  • 引入Ninject
  • 第二輪重構
  • 總結

場景描述 & 問題引出

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

列印出如下結果:

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

一、將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方法

二、使用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中去掉這些配置。

第二輪重構

通過創建、註冊依賴項解析器達到自動依賴項註入。

一、創建依賴項解析器

這裡的依賴項解析器所做的工作就是之前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));
    }        
}

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

祝學習進步:)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本章的內容來源是有朋友咨詢怎麼做微信公眾號信息的收發消息功能,因此本著為社區做貢獻的態度申請了個人公眾號,然後嘗試對接了一下接收公眾號內容信息的流程;要說對接其實呢也算不上,因為個人賬號只有簡單的一些接收,被動回覆等功能信息,不能群發和使用客服介面,所以本章主要分享的是怎麼接受信息和被動發送回覆信息 ...
  • 之前寒假時,試著使用jQuery寫了幾個非同步請求demo, 但是那樣是使用的webform普通頁面,一般應該是用 webservice 居多。 最近寫後臺管理時,想用非同步來實現一些信息的展示和修改, 這是第一次真正的在實際中運用ajax,卡了一個小時才做好簡單的信息展示。 在這之間遇到了兩個問題。寫 ...
  • C# 泛型集合之非泛型集合類與泛型集合類的對應: ArrayList對應List HashTable對應Dictionary Queue對應Queue Stack對應Stack SortedList對應SortedList 第一 : ArrayList(非泛型集合) 與List(泛型集合) Arra ...
  • 內部類,就是在類的內部定義的類......內部類訪問特點1,內部類可以直接訪問外部類的成員,包括私有...2,外部類要訪問內部類的成員,必須創建對象...外部類名.內部類名 對象名=外部類對象.內部類對象; class Test { public static void main(String[] ...
  • 一、Jenins+GitHub "參考" 另外需要配置Global Tool Configuration 如果沒有安裝git,需下載安裝, "下載地址" 二、jenkins發佈donet core應用 1.配置MSBuild 1.1安裝Visual Studio 2017生成工具, "下載地址" 1 ...
  • 使用 StackExchange.Redis 封裝屬於自己的 RedisHelper 目錄 核心類 ConnectionMultiplexer 字元串(String) 哈希(Hash) 列表(List) 有序集合(sorted set) Key 操作 發佈訂閱 其他 簡介 目前 .NET 使用訪問 ...
  • 下拉列表 以性別為例 綁定可以了,可以顯示了,但有些地方就能傳值,有些地方就會出錯提示,如有大神請指教。。。。 錯誤如下: 具有鍵“sex”的 ViewData 項屬於類型“YTgoShopping.Utilities.sex”,但它必須屬於類型“IEnumerable<SelectListItem ...
  • 一、屌絲也有春天 "親愛的,在不?" "妹子,你電腦又感覺慢了麽?您以後裝軟體的時候註意點行不,能不能不要裝上7-8個瀏覽器,3-4個殺毒軟體,啥配置的機子你都感覺卡。以後別到處瞎找動作類電影看,網上一般都掛馬騙你們這些小白的,實在想看找我要種子不就行了。" "沒有啦,人家電腦好著呢,您不是做軟體的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...