ASP.NET Core 2 學(xué)習(xí)筆記(四)依賴(lài)注入

ASP.NET Core使用了大量的依賴(lài)注入(Dependency Injection, DI)宋彼,把控制反轉(zhuǎn)(Inversion Of Control, IoC)運(yùn)用的相當(dāng)巧妙锥累。DI可算是ASP.NET Core最精華的一部分末患,有用過(guò)Autofac或類(lèi)似的DI Framework對(duì)此應(yīng)該不陌生质和。
本篇將介紹ASP.NET Core的依賴(lài)注入(Dependency Injection)柒瓣。

DI 容器介紹

在沒(méi)有使用DI Framework 的情況下喳魏,假設(shè)在UserController 要調(diào)用UserLogic,會(huì)直接在UserController 實(shí)例化UserLogic羡藐,如下:


public class UserLogic {
public void Create(User user) {
        // ...
    }
}

public class UserController : Controller {

    public void Register(User user){
        var logic = new UserLogic();
        logic.Create(user);
        // ...
    }
}

以上程序基本沒(méi)什么問(wèn)題,但是依賴(lài)關(guān)系差了點(diǎn)悯许。UserController 必須要依賴(lài)UserLogic才可以運(yùn)行仆嗦,拆出接口改成:


public interface IUserLogic {
    void Create(User user);
}

 

public class UserLogic : IUserLogic {
    public void Create(User user) {
        // ...
    }
}

 

public class UserController : Controller {
    private readonly IUserLogic _userLogic;
    public UserController() {
       _userLogic = new UserLogic();
    }

 

    public void Register(User user){
        _userLogic.Create(user);
        // ...
    }
}

UserController 與UserLogic 的依賴(lài)關(guān)系只是從Action 移到構(gòu)造方法中,依然還是很強(qiáng)的依賴(lài)關(guān)系先壕。

ASP.NET Core透過(guò)DI容器瘩扼,切斷這些依賴(lài)關(guān)系,實(shí)例的產(chǎn)生不在使用方(指上例UserController構(gòu)造方法中的new)垃僚,而是在DI容器集绰。
DI容器的注冊(cè)方式也很簡(jiǎn)單,在Startup.ConfigureServices注冊(cè)谆棺。如下:

Startup.cs


// ...

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }
}

services就是一個(gè)DI容器栽燕。
此例把MVC的服務(wù)注冊(cè)到DI容器,等到需要用到MVC服務(wù)時(shí)誊锭,才從DI容器取得對(duì)象實(shí)例立镶。

基本上要注入到Service的類(lèi)沒(méi)什么限制味榛,除了靜態(tài)類(lèi)。
以下示例就只是一般的Class繼承Interface:


public interface ISample
{
    int Id { get; }
}

 

public class Sample : ISample
{
    private static int _counter;
    private int _id;
 
    public Sample()
   {
        _id = ++_counter;
    }

    public int Id => _id;
}

要注入的Service需要在Startup.ConfigureServices中注冊(cè)實(shí)例類(lèi)型蔼啦。如下:
Startup.cs


// ...

public class Startup

{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
       services.AddScoped<ISample, Sample>();
    }
}

第一個(gè)泛型為注入的類(lèi)型
建議用Interface來(lái)包裝,這樣在才能把依賴(lài)關(guān)系拆除仰猖。
?第二個(gè)泛型為實(shí)例的類(lèi)型

DI 運(yùn)行方式

ASP.NET Core的DI是采用Constructor Injection捏肢,也就是說(shuō)會(huì)把實(shí)例化的物件從建構(gòu)子傳入。
如果要取用DI容器內(nèi)的物件饥侵,只要在建構(gòu)子加入相對(duì)的Interface即可鸵赫。例如:
Controllers\HomeController.cs


using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;
 
namespace MyWebsite.Controllers
{
    public class HomeController : Controller
    {
        private readonly ISample _sample;
        public HomeController(ISample sample)
        {
            _sample = sample;
        }

        public string Index()
        {

            return $"[ISample]\r\n"

                 + $"Id: {_sample.Id}\r\n"

                 + $"HashCode: {_sample.GetHashCode()}\r\n"

                 + $"Tpye: {_sample.GetType()}";
        }
    }
}

輸出內(nèi)容如下:

[ISample]
Id: 1
HashCode: 52582687
Tpye: MyWebsite.Models.Sample 

ASP.NET Core 實(shí)例化Controller 時(shí),發(fā)現(xiàn)構(gòu)造方法中有ISample 這個(gè)類(lèi)型的參數(shù)爆捞,就把Sample 的實(shí)例注入給該Controller奉瘤。
每個(gè)Request 都會(huì)把Controller 實(shí)例化,所以DI 容器會(huì)從構(gòu)造方法注入ISample 的實(shí)例煮甥,把sample 存到變量_sample 中盗温,就能確保Action 能夠使用到被注入進(jìn)來(lái)的ISample 實(shí)例。
注入實(shí)例過(guò)程成肘,情境如下:


1215970-20180524102210957-1502985396.png

Service 生命周期

注冊(cè)在DI 容器的Service 分三種生命周期:
?Transient
每次注入時(shí)卖局,都重新new一個(gè)新的實(shí)例。
?Scoped
每個(gè)Request都重新new一個(gè)新的實(shí)例双霍,同一個(gè)Request不管經(jīng)過(guò)多少個(gè)Pipeline都是用同一個(gè)實(shí)例砚偶。上例所使用的就是Scoped批销。
?Singleton
被實(shí)例化后就不會(huì)消失,程式運(yùn)行期間只會(huì)有一個(gè)實(shí)例染坯。

小改一下Sample 類(lèi)的代碼:


namespace MyWebsite.Models
{
    public interface ISample
    {
        int Id { get; }
    }

 
    public interface ISampleTransient : ISample
    {
    }

 

    public interface ISampleScoped : ISample
    {
    }

 

    public interface ISampleSingleton : ISample
    {

    }

 
    public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
    {
        private static int _counter;
        private int _id;

        public Sample()
        {
            _id = ++_counter;
        }

        public int Id => _id;
    }
}

在Startup.ConfigureServices中注冊(cè)三種不同生命周期的服務(wù)均芽。如下:


public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<ISampleTransient, Sample>();
        services.AddScoped<ISampleScoped, Sample>();
        services.AddSingleton<ISampleSingleton, Sample>();
        // Singleton 也可以用以下方法注冊(cè)
        // services.AddSingleton<ISampleSingleton>(new Sample());
    }
}

Service Injection

只要是透過(guò)WebHost產(chǎn)生實(shí)例的類(lèi)型,都可以在構(gòu)造方法注入单鹿。
所以Controller掀宋、View、Filter仲锄、Middleware或自定義的Service等都可以被注入劲妙。
此篇我只用Controller、View儒喊、Service做為范例镣奋。

Controller

在TestController 中注入上例的三個(gè)Services:

Controllers\TestController.cs

using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;

namespace MyWebsite.Controllers
{
    public class TestController : Controller
    {
        private readonly ISample _transient;
        private readonly ISample _scoped;
        private readonly ISample _singleton;

        public TestController(
            ISampleTransient transient,
            ISampleScoped scoped,
            ISampleSingleton singleton)
        {

            _transient = transient;

            _scoped = scoped;

            _singleton = singleton;

        }

        public IActionResult Index()
        {
            ViewBag.TransientId = _transient.Id;
            ViewBag.TransientHashCode = _transient.GetHashCode();
            ViewBag.ScopedId = _scoped.Id;
            ViewBag.ScopedHashCode = _scoped.GetHashCode();

 

            ViewBag.SingletonId = _singleton.Id;
            ViewBag.SingletonHashCode = _singleton.GetHashCode();
            return View();
        }
    }
}

Views\Test\Index.cshtml


<table border="1">
    <tr><td colspan="3">Cotroller</td></tr>
    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
    <tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
    <tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
    <tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>

輸出內(nèi)容如下:


1215970-20180524104230344-1150323681.png

從左到又打開(kāi)頁(yè)面三次,可以發(fā)現(xiàn)Singleton的Id及HashCode都是一樣的怀愧,現(xiàn)在還看不太能看出來(lái)Transient及Scoped的差異侨颈。

Service 實(shí)例產(chǎn)生方式:


1215970-20180524104924886-1888529082.gif

圖例說(shuō)明:
?A為Singleton對(duì)象實(shí)例
一但實(shí)例化,就會(huì)一直存在于DI容器中掸驱。
?B為Scoped對(duì)象實(shí)例
每次Request就會(huì)產(chǎn)生新的實(shí)例在DI容器中肛搬,讓同Request周期的使用方,拿到同一個(gè)實(shí)例毕贼。
?C為T(mén)ransient對(duì)象實(shí)例
只要跟DI容器請(qǐng)求這個(gè)類(lèi)型温赔,就會(huì)取得新的實(shí)例。

View

View注入Service的方式鬼癣,直接在*.cshtml使用@inject:

Views\Test\Index.cshtml

@using MyWebsite.Models
@inject ISampleTransient transient
@inject ISampleScoped scoped
@inject ISampleSingleton singleton


<table border="1">
    <tr><td colspan="3">Cotroller</td></tr>
    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
   <tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
    <tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
    <tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
<hr />
<table border="1">
    <tr><td colspan="3">View</td></tr>
    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
    <tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>
    <tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>
    <tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>

</table>

輸出內(nèi)容如下:


1215970-20180524105407300-1696309491.png

從左到又打開(kāi)頁(yè)面三次陶贼,Singleton的Id及HashCode如前例是一樣的。
Transient及Scoped的差異在這次就有明顯差異待秃,Scoped在同一次Request的Id及HashCode都是一樣的拜秧,如紅紫籃框。

Service

簡(jiǎn)單建立一個(gè)CustomService章郁,注入上例三個(gè)Service枉氮,代碼類(lèi)似TestController。如下:

Services\CustomService.cs

using MyWebsite.Models;
namespace MyWebsite.Services
{

    public class CustomService
    {

        public ISample Transient { get; private set; }
        public ISample Scoped { get; private set; }
        public ISample Singleton { get; private set; }

        public CustomService(ISampleTransient transient,
            ISampleScoped scoped,
            ISampleSingleton singleton)
        {

            Transient = transient;
            Scoped = scoped;
            Singleton = singleton;
        }
    }
}

注冊(cè)CustomService
Startup.cs


public class Startup

{

    public void ConfigureServices(IServiceCollection services)

    {

        // ...

        services.AddScoped<CustomService, CustomService>();

    }

}

第一個(gè)泛型也可以是類(lèi)暖庄,不一定要是接口聊替。
缺點(diǎn)是使用方以Class作為依賴(lài)關(guān)系,變成強(qiáng)關(guān)聯(lián)的依賴(lài)培廓。

在View 注入CustomService:

Views\Home\Index.cshtml


@using MyWebsite

@using MyWebsite.Models

@using MyWebsite.Services

 

@inject ISampleTransient transient

@inject ISampleScoped scoped

@inject ISampleSingleton singleton

@inject CustomService customService

 

<table border="1">

    <tr><td colspan="3">Cotroller</td></tr>

    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>

    <tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>

    <tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>

    <tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>

</table>

<hr />

<table border="1">

    <tr><td colspan="3">View</td></tr>

    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>

    <tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>

    <tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>

    <tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>

</table>

<hr />

<table border="1">

    <tr><td colspan="3">Custom Service</td></tr>

    <tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>

    <tr><td>Transient</td><td>@customService.Transient.Id</td><td>@customService.Transient.GetHashCode()</td></tr>

    <tr><td>Scoped</td><td>@customService.Scoped.Id</td><td>@customService.Scoped.GetHashCode()</td></tr>

    <tr><td>Singleton</td><td>@customService.Singleton.Id</td><td>@customService.Singleton.GetHashCode()</td></tr>

</table>

輸出內(nèi)容如下:


1215970-20180524110357485-889019439.png

從左到又打開(kāi)頁(yè)面三次:
?Transient
如預(yù)期惹悄,每次注入都是不一樣的實(shí)例。
?Scoped
在同一個(gè)Requset中肩钠,不論是在哪邊被注入泣港,都是同樣的實(shí)例暂殖。
?Singleton
不管Requset多少次,都會(huì)是同一個(gè)實(shí)例当纱。

參考

Introduction to Dependency Injection in ASP.NET Core
ASP.NET Core Dependency Injection Deep Dive

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呛每,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子惫东,更是在濱河造成了極大的恐慌莉给,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廉沮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡徐矩,警方通過(guò)查閱死者的電腦和手機(jī)滞时,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滤灯,“玉大人坪稽,你說(shuō)我怎么就攤上這事×壑瑁” “怎么了窒百?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)豫尽。 經(jīng)常有香客問(wèn)我篙梢,道長(zhǎng),這世上最難降的妖魔是什么美旧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任渤滞,我火速辦了婚禮,結(jié)果婚禮上榴嗅,老公的妹妹穿的比我還像新娘妄呕。我一直安慰自己,他們只是感情好嗽测,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布绪励。 她就那樣靜靜地躺著,像睡著了一般唠粥。 火紅的嫁衣襯著肌膚如雪疏魏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,785評(píng)論 1 290
  • 那天厅贪,我揣著相機(jī)與錄音蠢护,去河邊找鬼。 笑死养涮,一個(gè)胖子當(dāng)著我的面吹牛葵硕,可吹牛的內(nèi)容都是我干的眉抬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼懈凹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜀变!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起介评,我...
    開(kāi)封第一講書(shū)人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤库北,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后们陆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體寒瓦,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年坪仇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杂腰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡椅文,死狀恐怖喂很,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情皆刺,我是刑警寧澤少辣,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站羡蛾,受9級(jí)特大地震影響漓帅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜林说,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一煎殷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腿箩,春花似錦豪直、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至钧惧,卻和暖如春暇韧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浓瞪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工懈玻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乾颁。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓涂乌,卻偏偏與公主長(zhǎng)得像艺栈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子湾盒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • DI在.NET Core里面被提到了一個(gè)非常重要的位置湿右, 這篇文章主要再給大家普及一下關(guān)于依賴(lài)注入的概念,身邊有工...
    AI云棧閱讀 1,101評(píng)論 0 10
  • 團(tuán)隊(duì)開(kāi)發(fā)框架實(shí)戰(zhàn)—ASP.NET Core的依賴(lài)注入ASP.NET Core 1.0在設(shè)計(jì)上原生就支持和有效利用依...
    Bobby0322閱讀 1,242評(píng)論 1 1
  • 一罚勾、概念解釋 使用 .NET毅人,通過(guò) new 運(yùn)算符(即,new MyService 或任何想要實(shí)例化的對(duì)象類(lèi)型)調(diào)...
    不是隔壁的老王啊閱讀 1,892評(píng)論 0 0
  • 夏尖殃,滂沱大雨偃旗息鼓丈莺,幽林,蟬鳴鳥(niǎo)叫分衫,歡快的享受著清涼场刑。 驕陽(yáng),倔強(qiáng)掛在山頭蚪战,散發(fā)著一天之中最后的余威,卻被一習(xí)涼...
    漢江漁夫石泉閱讀 273評(píng)論 0 0
  • 今天的晨讀《愛(ài)的五種語(yǔ)言》相當(dāng)實(shí)用铐懊,可以堪稱(chēng)一部美滿(mǎn)婚姻實(shí)用手冊(cè)邀桑。 可為什么現(xiàn)在離婚率依然暴漲,家庭問(wèn)題那么多科乎,有...
    海星_love閱讀 115評(píng)論 1 4