在 ASP.NET 應用程式中,Unity 的 RegisterType 要寫在哪裡?

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 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 事件觸發時,再將子容器釋放掉。

關於子容器的運作方式,有兩點值得提一下:
  1. 呼叫子容器物件的 Resove 方法來解析(建立)物件時,如果子容器查無此型別,則會向它的父層查詢。
  2. 子容器一旦被釋放(呼叫其 Dispose 方法),該容器所管理之物件也會跟著被釋放。

綜合以上的說明,個人建議是盡量將型別註冊的動作放在 Application_Start 事件中,若有碰到特殊需求(歡迎大家提供實例),則可以嘗試在 Application_BeginRequest 事件中使用子容器的作法。

P.S. 請注意 Application_Start 事件只負責註冊型別,別將 Resolve 的動作也寫在這裡喔!Resolve 物件的動作通常應該寫在每次 request 都會觸發的事件裡,例如 Application_BeginRequest、Page_Load、按鈕事件等等 。

延伸閱讀

2 則留言:

  1. 相當感謝您的整理跟回覆 :)

    我想,型別的mapping甚至初始化,應該還是很難對performance造成多大的影響啦,之前的經驗是用Spring.NET,它初始化每個物件的動作,沒記錯的話,預設也是在process起來的時間點,把所有對應的物件先初始化好。

    加上ASP.NET WebSite有第一次運行比較久的問題,兩者放在一起,似乎就會有點感覺 XD

    anyway, 我跟老師也是一樣的想法,只是自己空想,有點驚驚 XD

    最後再次感謝您的整理與回覆,收穫很多 ^^

    回覆刪除
  2. 其實我也收獲不少,才要謝謝你勒 :)

    一直沒去碰 Spring.NET 這個好物,有機會接觸時, 得好好拜讀你的文章,學習一下才行!

    回覆刪除

技術提供:Blogger.
回頂端⬆️