[ASP.NET Web API]3 分鐘搞定 DI framework–Unity Application Block

  • 10308
  • 0

[ASP.NET Web API]3 分鐘搞定 DI framework–Unity Application Block

前言

DI/IoC 的設計前面已經講過好幾次了,簡單的一段話說明就是:「目標物件與外部相依的方式僅相依於 interface,而相依 interface 的 instance 透過 constructor 或 public property 來讓外部可以注入相依實體」。

而 DI framework 也是相當多種,這篇文章就簡單介紹怎麼在 Web API 專案中,簡單快速地 adopt Enterprise Library 中的 Unity 。

 

步驟

先建立一個 ASP.NET MVC project, 選擇 Web API ,會看到預設長出來的 Controller ,以 ValuesController 的 Get() 為例,程式碼如下所示:


        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

假設使用 IoC 的設計方式, ValuesController.Get() 方法是透過 IOrderService 這個介面來取值,那麼先新增一個 ValuesController 的 constructor ,參數傳入 IOrderService ,如下所示:


    public class ValuesController : ApiController
    {
        private IOrderService _orderService;
        public ValuesController(IOrderService orderService)
        {
            this._orderService = orderService;
        }

        // GET api/values
        public IEnumerable<string> Get()
        {
            //return new string[] { "value1", "value2" };
            return this._orderService.GetValues();
        }

    }

    public interface IOrderService
    {
        IEnumerable<string> GetValues();
    }

新增一個實作 IOrderService 的 concrete class, 稱為 JoeyOrderService ,程式碼如下所示:


    public class JoeyOrderService : IOrderService
    {
        public IEnumerable<string> GetValues()
        {
            return new string[] { "joey1", "joey2" };
        }
    }

假設需求是希望 ValuesController depend on IOrderService ,在實際上是注入 JoeyOrderService ,在使用時不需要再 new ValuesController(new JoeyOrderService) 這麼麻煩,拿到 ValuesController 時相關的相依物件都已經被初始化好了,我們只需要使用 DI framework,註冊 IOrderService 使用 JoeyOrderService 即可。

這邊使用 Unity 來實作這一段,請在 NuGet 加載 Unity.WebAPI

image

除了相關組件參考以外, NuGet 還加入了一支 Bootstrapper.cs ,打開來會看到程式碼如下:


using System.Web.Http;
using Microsoft.Practices.Unity;

namespace MvcUnitySample
{
    public static class Bootstrapper
    {
        public static void Initialise()
        {
            var container = BuildUnityContainer();

            GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        }

        private static IUnityContainer BuildUnityContainer()
        {
            var container = new UnityContainer();

            // register all your components with the container here
            // e.g. container.RegisterType<ITestService, TestService>();            

            return container;
        }
    }
}

從註解可以看到,只需要加入 container.RegisterType<TFrom, TTo>() 即可,這邊的例子只需要把 IOrderService 與 JoeyOrderService 註冊在一起即可,如下所示:


    public static class Bootstrapper
    {
        public static void Initialise()
        {
            var container = BuildUnityContainer();

            GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        }

        private static IUnityContainer BuildUnityContainer()
        {
            var container = new UnityContainer();
            
            container.RegisterType<IOrderService, JoeyOrderService>();
            return container;
        }
    }

接著打開 Global.asax.cs ,在 Application_Start() 的時候,呼叫 Bootstrapper.Initialise() 即可,如下所示:


    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            Bootstrapper.Initialise();
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

這樣就大功告成了。我們只做了幾件事:

  1. 把相依的 interface 拉到 constructor or public property ,供外部注入
  2. 透過 NuGet 加載 Unity.WebAPI
  3. 在 Bootstrapper 的 BuildUnityContainer() 中註冊 interface 對應的 instance type
  4. 在 Global.asax 中,呼叫 Bootstrapper 的初始化。

來看一下實際的結果,如下圖所示:

image

 

結論

為什麼還需要透過 DI framework 來決定相依物件呢?原因如下:

  1. 這件事不屬於 context 端負責,因為 context 端應該只負責用,而不負責生成目標物件。
  2. 當物件分割較為獨立時,使用一個物件,其相關相依介面可能不少,而對應該介面的實體,也可能還有相依其他介面。因此可能得一大串的 new 之後,才能正常使用一個相依物件。透過 DI framework 的 auto-wiring,會在生成物件的同時,檢查 constructor 所使用到的物件,或是標記需要注入的 public property,來產生對應的相依物件自動注入。這樣可以節省相當多生成物件的動作。

上面的例子是透過 constructor, 如果希望透過 property 來做注入,也相當簡單,只需要在 property 上標示:[Microsoft.Practices.Unity.Dependency]這個 Attribute 即可,程式碼如下所示:


    public class ValuesController : ApiController
    {
        //private IOrderService OrderService;
        //public ValuesController(IOrderService orderService)
        //{
        //    this._orderService = orderService;
        //}

        [Microsoft.Practices.Unity.Dependency]
        public IOrderService OrderService { get; set; }

        // GET api/values
        public IEnumerable<string> Get()
        {
            //return new string[] { "value1", "value2" };
            return this.OrderService.GetValues();
        }
    }

這樣子效果是一樣的。

現在 NuGet 已經相當方便了,如果已經有使用 IoC 的設計,就強烈建議使用方便的 DI framework 來解決生成物件跟相依物件的問題。

 

Reference

  1. IoC 設計的介紹:[30天快速上手TDD][Day 5]如何隔離相依性 - 基本的可測試性
  2. 董大偉老師介紹 Unity Application Block: MS Enterprise Library 6.0(一) - Unity Application Block
  3. 小風介紹在 Web API 使用 Autofac: 使用Asp.Net MVC打造Web Api (4) - 套用DI Framework – Autofac
  4. 黑暗執行緒介紹 Autofac: Autofac 筆記

blog 與課程更新內容,請前往新站位置:http://tdd.best/