91 兄在 Unity 入門 (5) 的留言板中提了一個實務上的問題,我原先沒有特別細想。由於留言板不方便編輯,所以我把整理後的回覆內容寫在這裡,方便日後參考。另一方面,則是順便補充先前入門系列所沒有提到的子容器用法。
如果把原先的問題一般化,就是:在 request-based 應用場合,例如 Web service、ASP.NET 應用程式等,DI 容器的型別註冊動作應該放在哪裡?
這裡先把一條基本原則搬出來:所有型別註冊的動作,都應該盡量放在應用程式的進入點來處理。
以 ASP.NET 來說,比較恰當的應用程式進入點就是 Application_Start 事件。把型別註冊的動作放在這裡面是很適合的。
可是如果有些型別就只有個別 request 會用到,卻將它們全部都集中在 Application_Start 事件中註冊,似乎會有影響效能,或分配不恰當的疑慮。
效能的部分,我沒有實際寫程式去測試,無法確知在 Application_Start 裡面要多少個註冊型別的動作才會對效能產生明顯衝擊。姑且假設程式挺複雜,總共會用到一兩百個型別對應。這樣的數量,我個人是覺得不太需要擔心效能問題的(也可能是我太天真啦)。
至於各 request 只要五毛,卻給五百的疑慮,則可以考慮用 Unity 容器的 CreateChildContainer 方法來解決。參考底下的 Global.asax 程式範例:
這樣的寫法可以解讀為:整個應用程式都會用到的型別,就在 Application_Start 中註冊;而單一 reuqest 才會用到的型別,則放在 Application_BeginRequest 裡面註冊,而且是註冊到子容器裡面。等到 request 結束時,亦即 Application_EndRequest 事件觸發時,再將子容器釋放掉。
關於子容器的運作方式,有兩點值得提一下:
綜合以上的說明,個人建議是盡量將型別註冊的動作放在 Application_Start 事件中,若有碰到特殊需求(歡迎大家提供實例),則可以嘗試在 Application_BeginRequest 事件中使用子容器的作法。
P.S. 請注意 Application_Start 事件只負責註冊型別,別將 Resolve 的動作也寫在這裡喔!Resolve 物件的動作通常應該寫在每次 request 都會觸發的事件裡,例如 Application_BeginRequest、Page_Load、按鈕事件等等 。
延伸閱讀
如果把原先的問題一般化,就是:在 request-based 應用場合,例如 Web service、ASP.NET 應用程式等,DI 容器的型別註冊動作應該放在哪裡?
這裡先把一條基本原則搬出來:所有型別註冊的動作,都應該盡量放在應用程式的進入點來處理。
以 ASP.NET 來說,比較恰當的應用程式進入點就是 Application_Start 事件。把型別註冊的動作放在這裡面是很適合的。
可是如果有些型別就只有個別 request 會用到,卻將它們全部都集中在 Application_Start 事件中註冊,似乎會有影響效能,或分配不恰當的疑慮。
效能的部分,我沒有實際寫程式去測試,無法確知在 Application_Start 裡面要多少個註冊型別的動作才會對效能產生明顯衝擊。姑且假設程式挺複雜,總共會用到一兩百個型別對應。這樣的數量,我個人是覺得不太需要擔心效能問題的(也可能是我太天真啦)。
至於各 request 只要五毛,卻給五百的疑慮,則可以考慮用 Unity 容器的 CreateChildContainer 方法來解決。參考底下的 Global.asax 程式範例:
<%@ Application Language="C#" %> <%@ Import Namespace="Microsoft.Practices.Unity" %> <script runat="server"> void Application_Start(object sender, EventArgs e) { IUnityContainer container = new UnityContainer(); container.RegisterType<ICanFly, Superman>(); container.RegisterType<ICanFly, Spiderman>("spiderman"); // ... more RegisterType call... Application["container"] = container; } void Application_End(object sender, EventArgs e) { // Code that runs on application shutdown var container = Application["container"] as IUnityContainer; if (container != null) { Application["container"] = null; container.Dispose(); } } protected void Application_BeginRequest(object sender, EventArgs e) { var container = Application["container"] as IUnityContainer; var childContainer = container.CreateChildContainer(); childContainer.RegisterType<ICanSwim, Fish>(); // 把子容器保存在 HttpContext 裡面,以便稍後用來解析(建立)物件. HttpContext.Current.Items["container"] = childContainer; } protected void Application_EndRequest(object sender, EventArgs e) { var container = HttpContext.Current.Items["container"] as IUnityContainer; if (container != null) { container.Dispose(); } } </script>
這樣的寫法可以解讀為:整個應用程式都會用到的型別,就在 Application_Start 中註冊;而單一 reuqest 才會用到的型別,則放在 Application_BeginRequest 裡面註冊,而且是註冊到子容器裡面。等到 request 結束時,亦即 Application_EndRequest 事件觸發時,再將子容器釋放掉。
關於子容器的運作方式,有兩點值得提一下:
- 呼叫子容器物件的 Resove 方法來解析(建立)物件時,如果子容器查無此型別,則會向它的父層查詢。
- 子容器一旦被釋放(呼叫其 Dispose 方法),該容器所管理之物件也會跟著被釋放。
綜合以上的說明,個人建議是盡量將型別註冊的動作放在 Application_Start 事件中,若有碰到特殊需求(歡迎大家提供實例),則可以嘗試在 Application_BeginRequest 事件中使用子容器的作法。
P.S. 請注意 Application_Start 事件只負責註冊型別,別將 Resolve 的動作也寫在這裡喔!Resolve 物件的動作通常應該寫在每次 request 都會觸發的事件裡,例如 Application_BeginRequest、Page_Load、按鈕事件等等 。
延伸閱讀
相當感謝您的整理跟回覆 :)
回覆刪除我想,型別的mapping甚至初始化,應該還是很難對performance造成多大的影響啦,之前的經驗是用Spring.NET,它初始化每個物件的動作,沒記錯的話,預設也是在process起來的時間點,把所有對應的物件先初始化好。
加上ASP.NET WebSite有第一次運行比較久的問題,兩者放在一起,似乎就會有點感覺 XD
anyway, 我跟老師也是一樣的想法,只是自己空想,有點驚驚 XD
最後再次感謝您的整理與回覆,收穫很多 ^^
其實我也收獲不少,才要謝謝你勒 :)
回覆刪除一直沒去碰 Spring.NET 這個好物,有機會接觸時, 得好好拜讀你的文章,學習一下才行!