於.net core 中有內建原生的 DI container,使得可以使用它的 IOC 機制。如果有複雜的情境,可能要於原生的機制上加上一些code,甚至可能不只一些。於是 Autofac 套件可以搭配複雜的情境需求,所以不需再重新加上額外的程式碼,或一堆 IF、else等等。
IOC 概念幫助我們節省了開發與維護的時間,是非常有感的。以往我們如果自己寫一個幫助自己專案的 IOC 工具,因為時間上的安排通常會做得比較直接簡約,能夠達成抽換(外部嵌入注入或專案內部物件注入)就算是達成目的,已經不錯了。作法大致上是使用一個static的集合來記錄Type與對應的設定要怎麼產生實例。
而許多套件方面卻又提供了更多的情境來使用。不但產生的實例可以注入到各種架構,還可搭配情境、生命週期、記憶體方面限制等等,使用上讓開發者感覺有非常豐富的支援。其中挑選了 Autofac 來使用與探討,原因是:
當然其它套件也有許多優點,用法也大致上都類似。用法幾乎可以用二個方向來描述 : 第一個是先註冊(Register)。第二個是實現實例(Resolve).
註冊:
builder.RegisterType<TextWriterLog>().AsSelf().As<ILog>().SingleInstance();
產生實例
ILog provider = null;
using (var scope = AutofacContainer.builder.BeginLifetimeScope())
{
if (scope.TryResolve<ILog>(out provider))
{
provider.WriteByLog(“\n” + DateTime.Now.ToString(“yyyy/MM/dd HH:mm:ss”) , “ILog write message!”);
}
}
如此即能實現簡單的IOC。
而 Autofac 還能夠讓產生實例的註冊再包含一些設定,如委派的函示、條件的安排,甚至其它情境如 Decorator pattern 與 Adapter pattern 的使用。這時會想,它是如何運作的? 它是怎麼達成的?
以下會以概述圖解方式展現。
Autofac 會用一個容器將所有資訊存入,並建各種引擎來做對應的事。最終 build 到 容器中。
先觀察註冊時的圖敘述:
(Autofac Container Register diagram - for IOC、DI)
在註冊服務這個動作中,最終要產生 RegistrationBuilder 這個物件, 包含了要如何變成實體的資料 ActivatorData 、變成什麼樣的服務 RegistrationData 與 其它協助過濾輔助的資料 RegistrationStyle。
其中的 RegistrationData.DeferredCallback,要存到 ContainerBuilder.configurationCallbacks List集合中,先Hook 執行內容 RegistrationBuilder.RegisterSingleComponent於此,也就是告訴容器這個服務未來要用此方式來產生製造實例的資訊。
接著看ContainerBuiler Build容器時,要產生容器與將資訊放入容器中:
(Autofac ContainerBuilder execute build process)
ContainerBuilder為建構容器的阿大引擎。之前透過此大引擎註冊好服務後,這邊開始做建構容器的動作。容器Container內有一個小引擎物件,ComponentRegistry, 它的作用是將ConponentRegistration存到自己的_registrations 與 _serviceinfo 集合內。透過Register方法先將預設的(新產生的必要服務) 轉至 ConponentRegistration 然後存到 自己的_registrations 與 _serviceinfo 集合。
接下來要將註冊好的資訊 (RegistrationBuilder) 將此內容由註冊時所Hook的驅動執行,產生ComponentRegistration存入ComponentRegistry的_registrations 與 _serviceinfo 集合內。
(Autofac ContainerBuilder execute build process cont.)
所以我們拉出視野,綜觀ContainerBuilder.build()在做的事:
先建立一個新的 Container。
產生新的ConponentRegistration,裡面放預設必要的基礎服務,放到 Container 的 ComponentRegistry 中的_registrations 與 _serviceinfo 集合。
將開發者註冊好的 RegistrationBuilder 轉成 ConponentRegistration 並逐一放到 Container 的 ComponentRegistry 中的_registrations 與 _serviceinfo 集合。
做好以上準備後,接下來就是於程式中去實現實例,以下圖稍作說明:
(Autofac Container execute Resolve process)
這邊看到有做遞迴,當一個實例的建構子參數還有需要用到所註冊的服務來產生實例,會接著再去跑一次抓服務資訊來對應並產生實例。所以其實它是可以巢狀地,一層一層的去注入已註冊好的服務。
如 new OutterClass( new MediamClass( new InnerClass()) , 此三個Class 皆可利用註冊服務的方式去resolve出來,而且可以神奇的只下一個指令:
container. Resolve<OutterClass>();
就可以幫忙產生好實例了,實在非常簡便,令很多國內外開發者讚嘆。
看到這邊,我相信要真的體會實際運作是有些技巧與想像。也就是仍有一點抽象。所以想了一個例子來做白話描述,讓大家可以想像得出來大致上的運作、作法,或者其實是類似做了什麼:
某人帶了一個籃子(container) , 買了放了各種蛋,並收集了要做哪些與蛋有關的菜單(service) ,貼上標籤,內容為蛋種類、配料與食譜,要準備做出哪個菜單,
標籤註明主要以快炒方式煮這些蛋(Hook RegistrationBuilder.RegisterSingleComponent ),
到了某餐廳(應用程式主體或某 controller action),
要找人做菜 (準備 resolve),
然後到找到某個有證照廚師,把這些資訊給他,請他做菜 (委派),
菜單要炒雞蛋, 依照標籤找到白雞蛋與火腿玉米配料,最後 快炒方式煮出火腿玉米炒蛋 (用火腿玉米裝飾了炒蛋,裝飾者模式)。
找到另一名廚師,這名廚師要先做到洗乾淨手與戴口罩(lambda expression and before prepare event)才行,然後請他做菜(委派),菜單要炒鹹蛋, 依照標籤找到鹹鴨蛋與苦瓜等配料,最後 快炒方式煮出苦瓜炒鹹蛋 (用苦瓜裝飾了炒蛋,裝飾者模式)。
之後要怎麼用這些煮好的成品看用途!
下一篇是介紹如何在此套件擴充客製的功能。