團(tuán)隊(duì)開發(fā)框架實(shí)戰(zhàn)—ASP.NET Core的依賴注入

團(tuán)隊(duì)開發(fā)框架實(shí)戰(zhàn)—ASP.NET Core的依賴注入
ASP.NET Core 1.0在設(shè)計(jì)上原生就支持和有效利用依賴注入。在Startup類中,應(yīng)用可以通過將框架內(nèi)嵌服務(wù)注入到方法中來使用他們;另一方面,你也可以配置服務(wù)來注入使用。默認(rèn)的服務(wù)容器只提供了最小的特性集合闷供,所以并不打算取代其他的IoC容器。

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ActDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnection")));

    services.AddMvc();

    services.AddTransient<IUnitOfWork, ActUnitOfWork>();
    services.AddTransient<IRepositoryBase<Role>, ActRepositoryBase<Role>>();
    services.AddTransient<IRoleAppService, RoleAppService>();
   
    AutoMapper.Configuration.Configure();
}

什么是依賴注入DI

依賴注入是為了達(dá)到解耦對(duì)象和其依賴的一項(xiàng)技術(shù)统诺。一個(gè)類為了完成自身某些操作所需的對(duì)象是通過某種方式提供的歪脏,而不是使用靜態(tài)引用或者直接實(shí)例化。通常情況下粮呢,類通過構(gòu)造器來聲明其依賴婿失,遵循 顯式依賴原則 。這種方式稱作構(gòu)造器注入啄寡。
當(dāng)以DI思想來設(shè)計(jì)類時(shí)豪硅,這些類更加松耦合,因?yàn)樗麄儾恢苯佑簿幋a的依賴其合作者挺物。這遵循了依賴倒置原則舟误,即高層模塊不應(yīng)依賴底層模塊,兩者都應(yīng)依賴抽象姻乓。類在構(gòu)建時(shí)所需是抽象(如接口interface),而不是具體的實(shí)現(xiàn)眯牧。把依賴抽離成接口蹋岩,把這些接口的實(shí)現(xiàn)作為參數(shù)也是 策略設(shè)計(jì)模式 的例子。
當(dāng)一個(gè)系統(tǒng)使用DI來設(shè)計(jì)時(shí)学少,很多類通過構(gòu)造器或者屬性來添加依賴剪个,這樣就很方便有一個(gè)專門的類來創(chuàng)建這些類以及他們相關(guān)的依賴。這樣的類稱之為“容器”或者“IoC容器”或“DI容器”版确。一個(gè)容器本質(zhì)上是一個(gè)工廠扣囊,來給請(qǐng)求者提供類型實(shí)例。如果給定類型聲明了自身依賴绒疗,容器也配置了來提供這些依賴類型侵歇,那么它會(huì)創(chuàng)建這些依賴作為請(qǐng)求實(shí)例的一部分。通過這種方式可以為 類提供復(fù)雜的依賴圖吓蘑,而不需要任何硬編碼的對(duì)象依賴惕虑。除了創(chuàng)建依賴對(duì)象外坟冲,容器一般還管理應(yīng)用內(nèi)的對(duì)象生命周期。
ASP.NET Core 1.0提供了一個(gè)簡單的內(nèi)置容器(以IServiceProvider為代表)溃蔫,默認(rèn)支持構(gòu)造器注入健提,這樣ASP.NET可以通過DI使某些服務(wù)可用。ASP.NET把它所管理的類型稱之為服務(wù)伟叛。本文的剩下部分私痹,服務(wù)即指ASP.NET IoC容器所管理的類型。你可以在Startup類中的ConfigureServices 方法中配置內(nèi)置的容器服務(wù)统刮。
Note:Martin Fowler寫過一篇很詳細(xì)的依賴反轉(zhuǎn)的 文章 紊遵。微軟對(duì)此也有很棒的描述 連接

總結(jié)一下IOC和DI

IOC:Inversion of Control网沾,即“控制反轉(zhuǎn)”癞蚕,他不是什么新的技術(shù),而是一種設(shè)計(jì)思想辉哥。
通常我們是這么理解桦山,我們一般的設(shè)計(jì)思想是在對(duì)象內(nèi)部直接控制,而IOC是將設(shè)計(jì)好的對(duì)象交給容器控制醋旦,而不是傳統(tǒng)的在對(duì)象內(nèi)部直接控制恒水。
打個(gè)比方:我們租房子,在我們和房主之間插入了一個(gè)中間人(房介)饲齐,我們只需要跟房介提出我們的要求钉凌,比如房子要三室一廳、臥室向陽捂人、房東是女的(_ )御雕、樓層不要太低、遮光不要太長等等等等滥搭,然后房介就會(huì)按照我們的要求給我們提供一個(gè)房產(chǎn)信息酸纲,我們滿意就跟租賃、入住瑟匆,如果我們不滿意(拋出異常)闽坡,房介就會(huì)幫我們做后續(xù)處理。整個(gè)過程不再是由我們控制愁溜,而是由房介這么一個(gè)容器去控制疾嗅。所有的類都會(huì)在spring容器中登記,告訴spring你是個(gè)什么東西冕象,你需要什么東西代承,然后spring會(huì)在系統(tǒng)運(yùn)行到適當(dāng)?shù)臅r(shí)候,把你要的東西主動(dòng)給你渐扮,同時(shí)也把你交給其他需要你的東西次泽。所有的類的創(chuàng)建穿仪、銷毀都由 spring來控制,也就是說控制對(duì)象生存周期的不再是引用它的對(duì)象意荤,而是spring啊片。對(duì)于某個(gè)具體的對(duì)象而言,以前是它控制其他對(duì)象玖像,現(xiàn)在是所有對(duì)象都被spring控制紫谷,所以這叫控制反轉(zhuǎn)。
DI:IOC的一個(gè)重點(diǎn)是在系統(tǒng)運(yùn)行中捐寥,動(dòng)態(tài)的向某個(gè)對(duì)象提供它所需要的其他對(duì)象笤昨。這一點(diǎn)是通過DI(Dependency Injection,依賴注入)來實(shí)現(xiàn)的握恳。比如對(duì)象A需要操作數(shù)據(jù)庫瞒窒,以前我們總是要在A中自己編寫代碼來獲得一個(gè)Connection對(duì)象,有了 spring我們就只需要告訴spring乡洼,A中需要一個(gè)Connection崇裁,至于這個(gè)Connection怎么構(gòu)造,何時(shí)構(gòu)造束昵,A不需要知道拔稳。在系統(tǒng)運(yùn)行時(shí),spring會(huì)在適當(dāng)?shù)臅r(shí)候制造一個(gè)Connection锹雏,然后像打針一樣巴比,注射到A當(dāng)中,這樣就完成了對(duì)各個(gè)對(duì)象之間關(guān)系的控制礁遵。A需要依賴 Connection才能正常運(yùn)行轻绞,而這個(gè)Connection是由spring注入到A中的,依賴注入的名字就這么來的佣耐。那么DI是如何實(shí)現(xiàn)的呢铲球? 有一個(gè)重要特征是反射(reflection),它允許程序在運(yùn)行的時(shí)候動(dòng)態(tài)的生成對(duì)象晰赞、執(zhí)行對(duì)象的方法、改變對(duì)象的屬性选侨,spring就是通過反射來實(shí)現(xiàn)注入的掖鱼。

使用框架提供的服務(wù)

ConfigureServices方法負(fù)責(zé)定義應(yīng)用使用的服務(wù),包括平臺(tái)特性服務(wù)如EF和ASP.NET MVC援制。最初提供給ConfigureServices的IServiceCollection只有少數(shù)服務(wù)戏挡。默認(rèn)web模板提供了怎么通過擴(kuò)展方法來添加額外服務(wù)到容器的例子,如AddEntityFramework, AddIdentity, 和AddMVC晨仑。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

ASP.NET提供的特性和中間件遵循使用一個(gè)AddService擴(kuò)展方法的約定褐墅,來注冊(cè)該特性使用的所需的所有服務(wù)拆檬。
Note:你可以在Startup方法中請(qǐng)求某些framework-provided服務(wù),詳見應(yīng)用啟動(dòng) Application Startup
當(dāng)然妥凳,除了配置框架提供的各種服務(wù)竟贯,你也可以配置自己定義的服務(wù)

注冊(cè)自定義服務(wù)

在默認(rèn)web模板中逝钥,有如下兩個(gè)服務(wù)被添加到IServiceCollection中

// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();

AddTransient方法將抽象類型映射為實(shí)體服務(wù)屑那,對(duì)于每個(gè)請(qǐng)求這都單獨(dú)實(shí)例化,這稱作服務(wù)的生命周期艘款。額外生命周期選項(xiàng)如下持际。對(duì)于每個(gè)注冊(cè)的服務(wù)選擇合適的生命周期是很重要的。是對(duì)每個(gè)請(qǐng)求類都提供一個(gè)新的實(shí)例化服務(wù)哗咆?還是在給定web請(qǐng)求內(nèi)只實(shí)例化一次蜘欲?還是在應(yīng)用周期內(nèi)只有單例?
在本文的例子中晌柬,有個(gè)簡單的CharacterController來顯示Character姓名姥份,在Index方法中顯示已存儲(chǔ)的Character(如果沒有則創(chuàng)建)。雖然注冊(cè)了EF服務(wù)空繁,但本例持久化沒有使用數(shù)據(jù)庫殿衰。具體的數(shù)據(jù)獲取服務(wù)抽象到了ICharacterRepository接口實(shí)現(xiàn)中,這遵從了 倉儲(chǔ)模式 盛泡。在構(gòu)造器中請(qǐng)求ICharacterRepository參數(shù)闷祥,并將其賦給私有變量,來根據(jù)需要獲取Character傲诵。

using System.Linq;
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Models;
using Microsoft.AspNet.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class CharactersController : Controller
    {
        private readonly ICharacterRepository _characterRepository;

        public CharactersController(ICharacterRepository characterRepository)
        {
            _characterRepository = characterRepository;
        }

        // GET: /characters/
        public IActionResult Index()
        {
            var characters = _characterRepository.ListAll();
            if (!characters.Any())
            {
                _characterRepository.Add(new Character("Darth Maul"));
                _characterRepository.Add(new Character("Darth Vader"));
                _characterRepository.Add(new Character("Yoda"));
                _characterRepository.Add(new Character("Mace Windu"));
                characters = _characterRepository.ListAll();
            }

            return View(characters);
        }
    }

接口ICharacterRepository只簡單定義了兩個(gè)方法凯砍,Controller通過其來操作Charcter實(shí)例。

using System.Collections.Generic;
using DependencyInjectionSample.Models;

namespace DependencyInjectionSample.Interfaces
{
    public interface ICharacterRepository
    {
        IEnumerable<Character> ListAll();
        void Add(Character character);
    }
}

接口有具體類型CharacterRepository來實(shí)現(xiàn)拴竹,在運(yùn)行時(shí)被使用悟衩。
Note:CharacterRepository類只是使用DI的普通例子,你可以對(duì)應(yīng)用所有的服務(wù)使用DI栓拜,而不僅僅是“倉儲(chǔ)”和數(shù)據(jù)獲取類座泳。

using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Models
{
    public class CharacterRepository : ICharacterRepository
    {
        private readonly ApplicationDbContext _dbContext;

        public CharacterRepository(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public IEnumerable<Character> ListAll()
        {
            return _dbContext.Characters.AsEnumerable();
        }

        public void Add(Character character)
        {
            _dbContext.Characters.Add(character);
            _dbContext.SaveChanges();
        }
    }
}

請(qǐng)注意,CharacterRepository在其構(gòu)造器中請(qǐng)求了ApplicationDbContext實(shí)例幕与。這種鏈?zhǔn)降囊蕾囎⑷胧呛艹R姷奶羰疲灰蕾嚤旧碛钟凶约旱囊蕾嚒H萜鱽碡?fù)責(zé)已樹形的方式來解析所有這些依賴啦鸣,并返回解析完成的服務(wù)潮饱。
Note:創(chuàng)建請(qǐng)求對(duì)象,以及其依賴诫给,其依賴的依賴香拉,有時(shí)這被稱之為 對(duì)象圖 啦扬。同樣的,需要解析的對(duì)象集合稱之為 依賴樹 或者 依賴圖 凫碌。
ICharacterRepository和ApplicationDbContext都必須在ConfigureServices中注冊(cè)扑毡。ApplicationDbContext是通過擴(kuò)展方法AddEntityFramework來配置,它包括添加DbContext (AddDbContext )的一個(gè)擴(kuò)展证鸥。倉儲(chǔ)的注視在在ConfigureServices方法的結(jié)尾僚楞。

services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddScoped<ICharacterRepository, CharacterRepository>();

// Show different lifetime options
services.AddTransient<IOperationTransient, Operation>();

EF contexts需要使用scoped生命周期來添加到服務(wù)容器。如果你使用了上面的helper方法枉层,這是已經(jīng)處理好的泉褐。使用EF的倉儲(chǔ)服務(wù)應(yīng)該使用同樣的生命周期。
警告: 主要不安全的來源是通過單例來解析Scoped生命周期服務(wù)服務(wù)鸟蜡。這樣做的后果膜赃,很有可能在處理后續(xù)請(qǐng)求時(shí)使用的服務(wù)的狀態(tài)是錯(cuò)誤的。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末揉忘,一起剝皮案震驚了整個(gè)濱河市跳座,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泣矛,老刑警劉巖疲眷,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異您朽,居然都是意外死亡狂丝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門哗总,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跋涣,“玉大人拟糕,你說我怎么就攤上這事∽⒁妫” “怎么了水评?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵沟启,是天一觀的道長挟冠。 經(jīng)常有香客問我携添,道長,這世上最難降的妖魔是什么叛本? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任沪蓬,我火速辦了婚禮,結(jié)果婚禮上炮赦,老公的妹妹穿的比我還像新娘。我一直安慰自己样勃,他們只是感情好吠勘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布性芬。 她就那樣靜靜地躺著,像睡著了一般剧防。 火紅的嫁衣襯著肌膚如雪植锉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天峭拘,我揣著相機(jī)與錄音俊庇,去河邊找鬼。 笑死鸡挠,一個(gè)胖子當(dāng)著我的面吹牛辉饱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拣展,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼彭沼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了备埃?” 一聲冷哼從身側(cè)響起姓惑,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎按脚,沒想到半個(gè)月后于毙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辅搬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年唯沮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伞辛。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烂翰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚤氏,到底是詐尸還是另有隱情甘耿,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布竿滨,位于F島的核電站佳恬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏于游。R本人自食惡果不足惜毁葱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贰剥。 院中可真熱鬧倾剿,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芹缔,卻和暖如春坯癣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背最欠。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工示罗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芝硬。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓蚜点,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吵取。 傳聞我的和親對(duì)象是個(gè)殘疾皇子禽额,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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