2020年10月24日 星期六

使用IoC與DI有何意義? (三) 在 Console 程式中使用DI

這一篇的重點在介紹,如何在Console App中使用DI,但我們的重點卻不是 Console,而是 “使用DI”。

看完前面兩篇,你應該要想到一個問題,如果PageModel或MVC的Controller,被呼叫的時候,.net core的DI服務會自動幫我們把Startup.cs中指定好的類別實作,自動注入PageModel(或Controller),那Console中的Main()也會嗎?

好比說,如果我把程式寫成底下這樣:
https://gist.github.com/isdaviddong/5faad4066919b4e5e72ae85621d021c6

我在Main()中能夠讀到 _SalaryFormula 的值嗎? 猜猜看?



答案是===> 不會。

原因很簡單,因為Console App的進入點是靜態的 Main()函式,它根本就是一切的源頭,是應用程式被Launch起來的時候,最最最開始的入口,根本不會有人去幫你 run 上面 5-8行的建構子,所以理所當然的也沒有注入這回事。

那…為何 PageModel可以? 為何MVC的Controller可以? 原因也不難,因為當用戶點選(或連結到)某一個頁面(或某一個Routing)的時候,該頁面(PageModel或Controller)所觸發的那隻程式(Onget() 或 Action),根本就不是整個程式的進入點。它(頁面)是"被"人家執行的。這個執行者不是用戶,是由 asp.net 的框架本身host的。

其實,整個 .net 應用程式的入口一直都是 Program.cs。

請看底下這個MVC Web應用程式的Program.cs:
enter image description here
事實上,Program.cs才是一切程式的進入點,而上面的Host,則是幫我們運行(Launch)起 asp,net web應用程式的背後推手。(未來有空我們再談這個類別)

一直以來,是有一個host幫我們承載每一個頁面的。當asp.net的某個頁面被呼叫,每一個controller被觸發,都不是該class被用戶主動觸發,而是『被』呼叫,被誰呼叫呢? 就是框架本身。正確的流程應該是,某個頁面(或說網址)被呼叫到時,asp.net框架中的底層(middleware, routing…etc.)得知該request,接收該http request的各種參數之後,幫我們new出我們所寫的程式碼(類別),然後把相關參數(像是QueryString, 或是http body…etc.)轉成parameters傳遞給我們的Action或Page。

這也是 asp.net MVC一堆預設的約定(慣例)的原因(的來源)

而我們一直說,DI被作為服務納入 asp.net core框架中成為內建機制的一部分,因此框架在幫我們呼叫(觸發)某一個頁面的時候,就順手(偷偷的)把我們想要注入的執行個體透過建構子的方式傳入,造就了這整個 DI"服務" 的誕生,就如同我們底下這段程式:
enter image description here
上圖中的PageModel,之所以可以實現DI,因為該頁面被(框架)呼叫時,由asp.net core框架幫我們new這個頁面的執行個體(這時當然會運行到該類別的建構子),然後框架順手幫我們把我們在Startup.cs中指定好的(適用於HR.ISalaryFormula)類別執行個體給注入(填入)建構子中作為參數,來實現所謂的DI注入服務,然後你在程式碼中就可以光明正大的使用了。

好了,到這邊你大概了解DI的原理了,那回頭看Console App大概也就明白了,因為Console App的Program.cs根本就是一切的起點,在這個起點中,根本沒有所謂的 asp.net core框架、也沒有host,所以沒人可以幫我們的main()實現注入這回事…

真正實現DI服務的是…?

所以要實現注入在Console App中的話,我們必須自己建立這個Host。這時,有一個現成的機制很好用,就是 ServiceCollection,請看底下這段code:
enter image description here
這是一段console App,不過請不要誤會了,它長的像是Main()但不是Main(),你可以說它是仿的main(),因為它的類別名稱是 MainApp(而非Program),且其中的Method Main()並非靜態。

上面這一段寫起來就很像是前面我們寫在 MVC Controller或是PageModel裡面的建構子,對吧。

並且,它能夠執行出我們想要的結果:
enter image description here

但請注意了,它並不是主程式(應該說它不是程式碼的進入點),我們的主程式其實依舊位於Program.cs中的這個main()靜態方法:
enter image description here

真正去 new出 MainApp這個類別的執行個體,並且調用MainApp中的Main()方法的是上圖中的第43行。簡單的說就是,這一行程式碼"幫你"建立(new)並執行了MainApp.Main()這個方法。

而33,35分別註冊了依賴注入所需要的型別參考。也因為是43行"幫你"執行的,所以它在幫你執行的時候,可以順手把你需要(定義過的)的依賴類別給注入(填入)MainApp的建構子當中,以至於所謂的DI服務可以實現。將來要換其他的公式時候,只須把35行改為37行這樣的寫法即可抽換。

再次提醒,其實是可以透過設定來抽換,不一定必須寫成程式碼。

這才是整個DI服務具體的實現方式。

結論

有沒有發現,DI之所以成為服務,是因為有人幫你做了許多事情。框架幫你做了,ServiceCollection幫你做了,所以我們才說,它(DI)在.net core中已成為了一種"服務(功能)",被納入到框架當中。那…我們可以自己做嗎?當然也行,具體的實現方式其實不難,如果只是要一個最簡單的實踐,其實只需要透過reflection機制就可以了。

好,這一段先到這邊,我們後面要準備來介紹更重要的內容…
source code :
https://github.com/isdaviddong/DotNetCoreConsoleDIServicesExample


本文內容來自於『團隊開發與架構設計實務』課程,我們最近要開課囉,依照過去經驗這門課幾乎都是秒殺,如果你需要預先保留席次,請點選這裡登記唷。

若您要詢問問題,或是需要即時取得更多相關訊息,可按這裡加入FB專頁。
若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

沒有留言:

熱門文章