牛刀小試一下 ASP.NET 5 內建的 DI 容器....

小引

在 ASP.NET 5(vNext)之前,亦即 MVC 4/5、Web API 2 的時代,MVC 與 Web API 框架彼此有非常相似的設計,卻是以不同的程式碼來實作。現在,ASP.NET 5 整合了 MVC、Web API 與Web Pages 程式模型於單一框架,統稱為 MVC 6。

ASP.NET 5 的另一個亮點是內建的 Dependency Injection(簡稱 DI)容器。在此之前的 MVC 與 Web API 框架對於 DI 的支援則相對薄弱,主角是 IDependencyResolver 介面。

ASP.NET 5 的內建 DI 容器可能已經能夠滿足大部分的 DI 基礎操作,這表示將來我們對其他 DI 框架(如 Unity、Autofac)的依賴程度可能會逐漸降低。

這篇筆記就要來牛刀小試一下 ASP.NET 5 內建的 DI 容器。

需要的工具
  • Visual Studio 2015 Preview

提醒您:由於 ASP.NET 5 仍在 beta 測試階段,Visual Studio 2015 也是預覽版,所以本文的操作畫面截圖和程式範例可能會跟將來的正式版有些出入。

Step 1:建立專案

開啟 Visual Studio 2015,建立一個新專案,參考下圖:


專案名稱命名為 DependencyInjectionDemo。按 OK 之後,接著選擇範本:


選擇 ASP.NET 5 Empty。

專案建立完成後,從 Solution Explorer 大概看一下裡面有哪些東西:


其中的 project.json 即是此專案的設定檔,其中包含了專案所依賴的框架與元件。Startup.cs 則包含了應用程式啟動時所需執行的初始化工作。

Step 2:加入必要組件

專案剛建立完成時的 project.json 內容如下圖:



在上圖中的 "dependencies" 區段中加入 ASP.NET MVC 組件:"Microsoft.AspNet.Mvc": "6.0.0-beta1"(你的開發環境可能是別的版本)。這些文字都要自己敲進去,不過還好,Visual Studio 有智慧提示功能,如下圖:

註:若沒出現智慧提示,可按 Alt+右方向鍵令它顯現。

輸入冒號之後,會接著提示版本:


如果移到最底下,就能看到最新可選擇的 beta 版本。但請注意,最新的 beta 版本可能無法在你目前的開發環境上正常運行。保險起見,還是選最上方的 "6.0.0-beta1"。

改完之後存檔,接著你可能會看到 Visual Studio 的 Output 視窗非常忙碌,安裝一堆東西,如下圖:


我們比較在意的是 "dependencies" 的內容:

    "dependencies": {
"Microsoft.AspNet.Server.IIS": "1.0.0-beta1",
"Microsoft.AspNet.Mvc": "6.0.0-beta1"
},

說明如下:
  • Microsoft.AspNet.Server.IIS - 由於我們要使用 IIS 來做為此應用程式的裝載平台,所以必須加入此套件。
  • Microsoft.AspNet.Mvc - 這是 MVC 與 Web API 的核心套件。

Step 3:將 Web API 元件加入 ASP.NET 管線

開啟 Startup.cs,參考以下範例來修改程式碼:
using System;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection; // 別忘了這個!
using Microsoft.AspNet.Hosting; // 別忘了這個!

namespace DependencyInjectionDemo
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
    }
}

說明如下:
  • Configure 方法有一個傳入參數 app,型別是 IApplicationBuilder。這裡使用它的 UseMvc 方法來將 MVC/Web API 元件加入至應用程式的管線作業流程。ASP.NET 框架會在應用程式啟動時主動呼叫此方法。
  • ConfigureServices 方法也會由 ASP.NET 框架主動呼叫,我們可以在這裡設定應用程式所需之服務(包括向 DI 容器註冊型別)。注意此方法的傳入參數 services,型別是 IServiceCollection,它就是 ASP.NET 5 內建的 DI 容器。我們可以透過這個容器來註冊相依物件的型別對應關係,這個部分稍後會有範例程式。
    註:IServiceCollection 介面所屬的命名空間是 Microsoft.Framework.DependencyInjection。

Step 4:加入 API Controller

在 Solution Explorer 中,專案的根目錄下建立一個資料夾:Controllers。然後在此資料夾上點右鍵,選 Add > New Item。在新開啟的對話窗中選擇「Web API Controller Class」,將檔案命名為 ValuesController.cs。參考下圖:


產生的程式碼大概長這樣:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc;

namespace DependencyInjectionDemo.Controllers.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET: api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // 省略其餘 Get/Post/Put/Delete 方法。
    }
}

OK! 現在按 F5 或 Ctrl+F5,看看應用程式能否正常運作。請注意,瀏覽器的網址列必須手動修改成這樣:

http://[主機:埠號]/api/Values

若沒出現錯誤訊息,就可以繼續下一個步驟。

Step 5:撰寫測試用的服務類別

寫一個簡單的類別來作為注入至 controller 的物件。如下所示:

namespace DependencyInjectionDemo
{
    public interface ITimeService
    {
        string Now { get; }
    }

    public class TimeService : ITimeService
    {
        public string Now
        {
            get
            {
                return DateTime.Now.ToString();
            }
        }
    }
}

以上程式碼很簡單,就不多解釋了。

Step 6:注入相依物件至 Controller 的建構函式

修改 ValuesController 類別,讓它看起來像這樣:

    public class ValuesController : Controller
    {
        private readonly ITimeService _timeService;

        public ValuesController(ITimeService timeService)
        {
            _timeService = timeService;
        }

        // GET: api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { _timeService.Now };
        }

        // 省略其餘 Get/Post/Put/Delete 方法。
    }

這表示,我們希望 ASP.NET 框架在建立此 controller 物件時,能夠一併注入它需要的 ITimeService 物件。用 DI 術語來說,這裡使用了「建構式注入」(Constructor Injection)的模式來避免我們的 API Controller 跟特定實作類別綁太緊——ValuesController 依賴的是抽象的 ITimeService 介面,而非具象類別 TimeService。

再執行一次應用程式看看,由於 MVC 框架找不到 ValuesController 的預設建構函式,瀏覽器應該會顯示錯誤訊息,如下圖:


解決方法很簡單,只要在你的 Startup 類別的 ConfigureServices 方法中註冊 ITimeService 型別所對應的實作類別就行了。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.AddScoped<ITimeService, TimeService>(); // 加這行!
    }
}

這裡是透過 IServiceCollection 的擴充方法 AddScope 來向 ASP.NET 內建的容器註冊型別對應關係:碰到需要 ITimeService 物件的時候,使用 TimeService 來建立物件實體。此外,從方法的名稱大約可以猜得出來,這個 AddScope 方法還有另一個意義,那就是:將來容器在建立 ITimeService 物件時(用 DI 術語來說,就是「解析 ITimeService」),會建立一個「活在特定範圍內」的物件。就此範例而言,這個特定範圍就是一個 HTTP 請求的範圍(DI 術語:Per Request 生命週期)。

如此一來,當 MVC 框架在建立 ValuesController 時,發現它的建構函式需要一個 ITimeService 物件,於是 MVC 框架就會要求此內建容器提供一個 ITimeService 物件,然後將此物件傳入 ValuesController 的建構函式。

再執行一次應用程式,這次應該能夠順利執行了。執行結果如下圖:


很簡單吧?這裡完全沒用到第三方 DI 框架。

運用相同技巧,你可以將任何物件注入至你的 Controller 類別,例如應用程式層(application layer)的各類服務/元件。

結語

ASP.NET vNext 的內建容器支援四種生命週期模式:Instance、Singleton、Transient、Scoped。本文範例中使用的 AddScoped 方法即為 Scoped,或者說 Per Request 生命週期模式(物件只存活在目前請求的範圍內,且同一請求範圍內會共享同一個物件實體)。

如欲進一步了解 ASP.NET vNext 的 DI 功能(與限制),可參考文後附的一些文章連結。

附註:我打算把這篇筆記稍加補充,整理到《.NET 相依性注入》書裡,暫且放在附錄二。(近日有五折優惠活動喔!)

參考資料