Unity 入門 (5)

前篇,這次要說明如何用組態檔來設定 Unity 容器的型別對應。


先前的幾個範例,都是以程式碼來設定 Unity 的型別對應關係,這種以程式碼來設定元件組態的方式(Code as Configuration)只是 Unity 的其中一種用法。官方說明文件對此作法有另一個稱呼,叫作「執行時期組態」(run-time configuration)。

相對於執行時期組態,Unity 也有提供設計時期組態(design-time configuration)的作法,也就是利用組態檔來完成相關設定。這種作法的最大好處就是調整設定時只需修改組態檔,而不用重新編譯程式。

Unity 的組態設定是放在 .NET 標準的應用程式組態檔案裡,因此是以 XML 格式來編寫。在使用 Unity 組態 API 時,我們的專案至少要加入下列組件參考:

  • System.Configuration
  • Microsoft.Practices.Unity.Configuration

除此之外,程式中也要引用(using)下列命名空間:

  • Systmtem.Configuration
  • Microsoft.Practices.Unity
  • Microsoft.Practices.Unity.Configuration

然後,我們就能夠在程式中呼叫 Unity 容器的擴充方法 LoadConfiguration 來從組態檔載入相關設定。例如:

    IUnityContainer container = new UnityContainer();
    container.LoadConfiguration();

LoadConfiguration 方法會從 .NET 應用程式組態檔中載入 Unity 相關組態,並且設定容器物件。

TIP  若使用 NuGet 來為專案加入 Unity 套件,NuGet 會自動幫你加入必要的組件參考。
接著當然就是撰寫組態檔了。在應用程式組態檔案中,我們得先加入一個自訂區段,以指定要用哪個類別來剖析 Unity 組態內容。這個自訂區段的名稱是「unity」,如下:

<configSections>
  <section name="unity"
           type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
                 Microsoft.Practices.Unity.Configuration"/>
</configSections>

接著我們可以再加入一個 unity 組態區段,把 Unity 相關設定寫在這個區段裡。以先前的範例來說,組態內容會是:

  <unity>
    <namespace name="UnityDemo01" />
    <assembly name="UnityDemo01" />
    <container>
      <register type="ISayHello" mapTo="SayHelloInChinese" />
    </container>
  </unity>

其中的 register 元素就是用來設定介面與實作類別的對應關係。由於前面已經有先用 namespace 元素宣告了命名空間的名稱,所以在 register 元素中的型別名稱就不用寫全名。

接著修改應用程式的進入點,也就是 Main 函式。

...
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
 
namespace UnityDemo01
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();
            container.LoadConfiguration();
            ISayHello hello = container.Resolve<ISayHello>();
            hello.Run();
        }
    }
}

你可以看到,其中只有用到抽象型別 ISayHello,而沒有任何實作類別,完全是針對介面來寫程式。(呼~終於走到這一步啦!)

執行結果跟先前看過的範例一樣:




執行時期組態 vs. 設計時期組態

雖然用組態檔來控制介面與實作類別的對應有其方便之處──無須重新編譯程式碼就能抽換實作類別──但它也不是沒有缺點。相較於利用程式碼來設定組態的作法,組態檔比較容易出錯,而且可能更不好維護。至於不利維護,這點恐怕就見仁見智了。我總覺得 XML 格式不利人眼閱讀,尤其當組態檔越長越大時。

因此,最好只有在真的需要不重新編譯程式就任意抽換類別的情況才使用組態檔;其餘的場合,還是優先考慮使用程式碼來設定型別對應。這裡特別強調「真的需要」,是因為我們有時候會不加思索地將所有東西一股腦兒塞進組態檔案裡,或者認為「任何東西都可以隨時抽換就是最好最彈性的作法。」其實這都值得商榷。

當然,執行時期組態(Code as Configuration)和設計時期組態(XML 組態檔)這兩種作法並不互相排斥,它們是可以一起搭配運用的。例如我們可以將大部分的型別對應都以程式碼來設定,而少數需要隨時任意切換的類別則放在組態檔案裡。這是一種搭配方式。

還有一種更靈活的搭配。還記得上一篇提過的「後來居上原則」嗎?利用此特性,你可以用程式碼來設定所有的型別對應關係,之後再接著呼叫載入 XML 組態的方法。這會讓 XML 組態設定蓋掉原先程式碼的設定,而且是只有重複的部分才蓋掉,對於 XML 組態沒有涵蓋的部分,仍然會使用程式碼的設定。相反地,如果有些型別對應是絕對不允許別人透過組態檔亂改的,就可以在程式中先載入 XML 組態,然後才呼叫 RegisterType 方法來註冊這些型別。

小結

五篇筆記不算多,但是對於入門來說,我想應該差不多足夠了。當然,Unity 還有更多進階功能和用法,若要繼續深入,可以接著了解 Unity 如何管理物件的生命週期

Unity 入門系列就先寫到這裡吧!

12 則留言:

  1. 請問一下老師,如果是在Web中,是否得在每一次的Request都去建立/註冊相關的type mapping呢?

    回覆刪除
  2. Hi 91!
    多謝你提出這個問題!
    就我所了解,如果是 Web app,可以在 Application_Start 事件中把所有註冊型別的工作一次處理完,我個人也建議這麼做。但是 Resolve 物件的動作則以 per-request 來處理比較好,例如在 Application_BeginRequest 事件中建立物件,然後在 Application_EndRequest 事件中 Dispose 物件。

    回覆刪除
  3. 謝謝老師解惑。

    想延伸再請教一下,那樣子做,是否代表需要透過static 物件來存放 Unity 的 container 呢?

    會不會有可能導致在 Application_Start 或 BeginRequest ,需要 mapping 太多東西,但其實該 request 需要用到的型別並沒有這麼多?

    因為自己沒有在 Web application 用過 Unity ,所以可能問的問題很粗淺,請見諒。

    回覆刪除
  4. 上一則不小心送出去了,擔心的是會不會有相關 performance的問題,每一個request都做這一件事,會不會有無謂的performance effort。

    回覆刪除
  5. 91 兄別客氣,你的問題很好啊!我剛剛在公車上也還在想這個問題。我的回答是過於簡略了,還有細節值得討論。容我再補充一下....

    如果某個 request 就只會用到兩三個型別對應,可是 Application_Start 裡面註冊了"很多"行別,這樣聽起來,的確會引發效能問題的疑慮,以及 91 所提到的,好像有點浪費、沒有效率的感覺。

    我將 "很多" 加註引號,是因為這個很多到底是多少才會引發效能問題,我還沒沒有實際寫程式去測試。姑且假設程式挺複雜,總共會用到一兩百個型別對應。這樣的數量,我是覺得不太需要擔心效能問題的。

    至於各 request 只要五毛,卻給五百的情形。則可以用 Unity 容器的 CreateChildContainer 來解決。也就是說,各 request 都會用到的型別,在 Application_Start 中註冊。而單一 reuqest 才會用到的型別,就在 Application_BeginRequest 裡面註冊,但這部分就真的別"太多",因為你知道,它每次 request 都要跑一次的。

    留言板不太適合貼程式碼,有關 child container 的部分,也許會連同前面的內容再加工整理到另一篇網誌吧。因為你的這個問題,我想有在用或打算用 DI 容器的人應該都會碰到的。
    Thanks!!

    回覆刪除
  6. 請問老師?關於組態檔的 unity 區段設定,其中 與
    是否可以多個?

    回覆刪除
  7. 您的問題不太完整,可以再說詳細一點嗎?
    (如果留言內含小於和大於符號,可能會被當作標籤來處理)

    回覆刪除
  8. 老師我想請問您一下(雖然很期待您新書快出來再看),我目前使用這個方式來做,我的專案是winform的,在Form這邊沒問題,但我在service這邊,使用相同的方式去用unity幫我注入Model,但卻會有錯誤,錯誤訊息是如下
    unity resolution of the dependency failed type name none winform
    如果要我提供程式碼的話,我可以提供給您....................

    回覆刪除
  9. 可以啊。可是範例程式請盡量簡單,去除其他不必要的程式碼,只保留重點的部分喔。最重要的是,很容易就能重現你說的錯誤現象。可以上傳至你的雲端硬碟,然後把下載連結貼上來嗎?或者貼到 MSDN 論壇也行(說不定有其他朋友更快出手幫您解決)。

    回覆刪除
  10. 已附上連結,這個只是剛開始的專案,所以非常簡單,只留下主專案和service和model而已,希望老師能指點一下,因為網路上對於winform的討論很少.............
    https://drive.google.com/file/d/0B5nZJYKR20DOTkVsaGotUWtIZzg/edit?usp=sharing

    回覆刪除
  11. 請在你的 Cooper 專案的 App.Config 裡面加入底下這行:
    <register type="Model.Dal.IOwnExpenseDDal, Model" mapTo="Model.Dal.OwnExpenseDDal, Model" />
    這樣就行了。我把整個 Unity 區段貼到這裡: https://gist.github.com/huanlin/e8278d0f6d5c969b6ba4

    回覆刪除

技術提供:Blogger.