ASP.NET Core依赖注入初识与思考
一、前言
在上一篇中,我們講述了什么是控制反轉(zhuǎn)(IoC)以及通過哪些方式實(shí)現(xiàn)的。這其中,我們明白了,「控制反轉(zhuǎn)(IoC)」 是一種軟件設(shè)計(jì)的模式,指導(dǎo)我們設(shè)計(jì)出更優(yōu)良,更具有松耦合的程序,而具體的實(shí)現(xiàn)方式有「依賴注入」和「依賴查找」。
在上篇實(shí)例中,我們通過日志的方式舉例說明,其中通過代碼創(chuàng)建了一個ILogger的接口,并實(shí)現(xiàn)接口實(shí)例,基于控制反轉(zhuǎn)的模式,依賴的創(chuàng)建也移交到了外部,但是也發(fā)現(xiàn)存在了問題,如果類似存在這樣多個接口和實(shí)現(xiàn)類,依賴太多,一一創(chuàng)建,沒有統(tǒng)一的管理,這反而增加了實(shí)際工作量麻煩。
因此我們需要一個可以統(tǒng)一管理系統(tǒng)中所有的依賴的地方,因此,IoC容器誕生了。
容器負(fù)責(zé)兩件事情:
綁定服務(wù)與實(shí)例之間的關(guān)系
獲取實(shí)例,并管理實(shí)例對象生命周期(創(chuàng)建與銷毀)
所以在這一篇中,我們主要講述Asp.Net Core中內(nèi)置的IoC容器。
二、說明
在Asp.Net Core中已經(jīng)為我們集成提供了一個內(nèi)置的IoC容器,我們可以看到在Startup.cs的ConfigureServices中,涉及到依賴注入的核心組件,一個「負(fù)責(zé)實(shí)例注冊」的IServiceCollection和一個「負(fù)責(zé)提供實(shí)例」的IServiceProvider
?簡單的說就是兩步:1. 把實(shí)例注冊到容器中;2. 從容器中獲取實(shí)例
?而在這其中,IServiceCollection為實(shí)現(xiàn)將開發(fā)者定義好的實(shí)例注冊進(jìn)去提供了「三種方法」。
分別是:
「AddTransient」 ?、「AddScoped」 、「AddSingleton」
而這三種不同實(shí)例方法也對應(yīng)的著三種不同的實(shí)例「生命周期」。
「三種不同的生命周期如下:」
2.1 暫時性
「AddTransient」
每次在向服務(wù)容器進(jìn)行請求時都會創(chuàng)建新的實(shí)例,這種生存期適合輕量級、 無狀態(tài)的服務(wù)。
2.2 作用域內(nèi)
「AddScoped」
在每次Web請求時被創(chuàng)建一次實(shí)例,生命周期橫貫整次請求。
?局部單例對象, 在某個局部內(nèi)是同一個對象(作用域單例,本質(zhì)是容器單例);一次請求內(nèi)是一個單例對象,多次請求則多個不同的單例對象。
?2.3 單例
「AddSingleton」
創(chuàng)建單例生命周期服務(wù)的情況如下:
在首次請求它們時進(jìn)行創(chuàng)建;
或者在向容器直接提供實(shí)現(xiàn)實(shí)例時由開發(fā)人員進(jìn)行創(chuàng)建。很少用到此方法。
其后的每一個后續(xù)請求都使用同一個實(shí)例。如果開發(fā)者的應(yīng)用需要單例服務(wù)情景,推薦的做法是交給服務(wù)容器來負(fù)責(zé)單例的創(chuàng)建和生命周期管理,而不是手動實(shí)現(xiàn)單例模式然后由開發(fā)者在自定義類中進(jìn)行操作。
?不要從單一實(shí)例解析指定了作用域的服務(wù)。當(dāng)處理后續(xù)請求時,它可能會導(dǎo)致服務(wù)處于不正確的狀態(tài)。可以從范圍內(nèi)或暫時性服務(wù)解析單一實(shí)例服務(wù)。
?三、開始
3.1 接口
定義三個接口,分別測試Singleton,Scope,Transient三種,一個 TestService服務(wù)
????public?interface?ITransientService{string?GetGuid();} ????public?interface?IScopedService{string?GetGuid();}????public?interface?ISingletonService{string?GetGuid();} ????public?interface?ITestService??{public??string?GetSingletonID();public?string?GetTransientID();public?string?GetScopedID();}3.2 實(shí)現(xiàn)
根據(jù)上面定義的幾種接口,并一一實(shí)現(xiàn)對應(yīng)的接口
????public?class?TransientService?:?ITransientService{public?string?OperationId?{?get;?}public?TransientService(){OperationId?=?Guid.NewGuid().ToString()[^4..];}public?string?GetGuid(){return?$"這是一個 Transient service :?"?+?OperationId;}}????public?class?ScopedService?:?IScopedService{public?string?OperationId?{?get;?}public?ScopedService(){OperationId?=?Guid.NewGuid().ToString()[^4..];}public?string?GetGuid(){return?$"這是一個 scoped service :?"+?OperationId;??}} ????public?class?SingletonService?:?ISingletonService{public?string?OperationId?{?get;?}public?SingletonService(){OperationId?=?Guid.NewGuid().ToString()[^4..];}public?string?GetGuid(){return?$"這是一個 Singleton service :?"?+?OperationId;}} ????public?class?TestService?:?ITestService{private?ITransientService?_transientService;private?IScopedService?_scopedService;private?ISingletonService?_singletonService;public?TestService(ITransientService?transientService,?IScopedService?scopedService,?ISingletonService?singletonService){_transientService?=?transientService;_scopedService?=?scopedService;_singletonService?=?singletonService;}public?string?GetSingletonID(){return?_singletonService.GetGuid();}public?string?GetTransientID(){return?_transientService.GetGuid();}public?string?GetScopedID(){return?_scopedService.GetGuid();}}3.3 注入
在Startup.cs類文件ConfigureServices方法中,注入依賴
????????public?void?ConfigureServices(IServiceCollection?services){services.AddControllers();services.AddTransient<ITransientService,?TransientService>();services.AddSingleton<ISingletonService,?SingletonService>();services.AddScoped<IScopedService,?ScopedService>();services.AddScoped<ITestService,?TestService>();}3.4 調(diào)用
定義一個控制器,實(shí)現(xiàn)調(diào)用
[ApiController] [Route("[controller]")] public?class?TestController?:?ControllerBase {private?ITransientService?_transientService;private?IScopedService?_scopedService;private?ISingletonService?_singletonService;?private?ITestService?_testService;public?TestController(ITransientService?transientService,?IScopedService?scopedService,?ISingletonService?singletonService,?ITestService??testService){_transientService?=?transientService;_scopedService?=?scopedService;_singletonService?=?singletonService;_testService?=?testService;}[HttpGet]public?JsonResult?Get(){var?data1?=???_transientService.GetGuid();var?data2?=?_testService.GetTransientID();var?data3?=?_scopedService.GetGuid();var?data4?=?_testService.GetScopedID();var?data5?=?_singletonService.GetGuid();var?data6?=?_testService.GetSingletonID();return?new?JsonResult(new?{?data1,?data2,?data3?,data4,data5,data6,});} }在上面中我們了解到,注入的方式一般有三種,構(gòu)造函數(shù)注入, 方法注入,屬性注入,而在ASP.NET Core中自帶的這個IoC容器,默認(rèn)采用了構(gòu)造函數(shù)注入的方式。
3.5 測試
啟動運(yùn)行項(xiàng)目,訪問接口/Test
效果如下:
3.6 對比
對比兩次的請求訪問可以發(fā)現(xiàn),上面我們一共得到了 4個Transient實(shí)例,2個Scope實(shí)例,1個Singleton實(shí)例。
「在請求中,AddSingleton方式的id值相同;」
「AddScope方式兩次請求之間不同,但同一請求內(nèi)是相同的;」
「AddTransient方式在同一請求內(nèi)的多次注入間都不相同。」
3.7小結(jié)
通過上述的代碼示例,也證實(shí)了之前的三種方式對應(yīng)不同生命周期的說明。
暫時性(Transient) : 生命周期是每次獲得對象都是一次新的對象,每一次都不一樣。
作用域內(nèi)(Scoped) : 生命周期是在每一次請求作用域內(nèi)是同一個對象,非作用域內(nèi)則是新的對象。
單例(Singletion) : 生命周期是這個服務(wù)啟動后都是一個對象,也即是全局單例對象。
四、其他Ioc容器
通過上述的了解,我們知道IoC容器是一個依賴注入框架,在.NET Core中也提供了內(nèi)置的IoC容器,通過AddXXX方法來實(shí)例依賴對象,而在實(shí)際開發(fā)中,就是每一個實(shí)例都需要一個個的添加,這樣的實(shí)現(xiàn)方式在小項(xiàng)目中還好,但是如果在復(fù)雜大型的項(xiàng)目中,就略向麻煩些,可能需要添加很多方法來實(shí)現(xiàn),整體的可觀性也不好。
為了達(dá)到可以簡化我們工作量,應(yīng)該采用批量注冊,因此我們也可以引入其他的Ioc容器框架,實(shí)現(xiàn)更多的功能和擴(kuò)展。
在平時開發(fā)中,常用的IoC框架有很多,而在這里我們選擇用Autofac,這也是在.net下比較流行的,其他的框架不做說明,可自行查閱了解。
「ASP.Net Core中使用Autofac 框架注入 (在后續(xù)篇章會具體說明)」
五、思考
在實(shí)際的開發(fā)中,對于這幾種生命周期,我們應(yīng)該如何應(yīng)用呢?
比如,在像DBContext這種實(shí)例,在實(shí)際開發(fā)中是用Transient 還是Scoped呢?
?「建議使用Scoped」
?因?yàn)橛行ο笤谡埱笾锌梢孕枰玫蕉鄠€方法或者多個Service、Repository的時候,為了減少實(shí)例初始化的消耗,實(shí)現(xiàn)事務(wù)功能,可以在整個請求的生命周期共用一個Scope。
其他的思考:
ASP.NET Core中,默認(rèn)采用了構(gòu)造函數(shù)注入的方式,如果采用屬性注入或者方法注入,又該怎么實(shí)現(xiàn)?
一個接口多種實(shí)現(xiàn)的時候,我們將多種實(shí)現(xiàn)都給注入進(jìn)了依賴注入容器中,但是在服務(wù)調(diào)用的時候總是獲取到最后注入的那個方法的實(shí)現(xiàn),這時候就在想能不能實(shí)現(xiàn)動態(tài)的選擇使用哪種實(shí)現(xiàn)呢?
一個作用域(Scoped)服務(wù)中注入一個瞬時(Transient)服務(wù)時,瞬時服務(wù)中的值還會每次都變化嗎?
鏈?zhǔn)阶⑷霑r,生存期的選擇,三種注入方式的權(quán)重問題
以上的思考,大家可以說說自己的想法。
「在后續(xù)的篇章中,也會對這些問題,進(jìn)行深入討論說明。」
六、總結(jié)
本篇主要介紹了什么是IoC容器,了解到它是DI構(gòu)造函注入的框架,它管理著依賴項(xiàng)的生命周期以及映射關(guān)系,同時也介紹實(shí)踐了在ASP.Net Core中,默認(rèn)提供的內(nèi)置IoC容器,以及它的實(shí)例注冊方式和相應(yīng)的生命周期。
好啦,這篇文章就先講述到這里吧,「在后續(xù)篇章中會對ASP.Net Core中使用Autofac 框架實(shí)踐說明」,希望對大家有所幫助。
如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學(xué)習(xí),共同進(jìn)步。????
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core依赖注入初识与思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 异步,你也许不知道的5种用法
- 下一篇: 二分查找和折半插入排序一块说说-很合适~