小引
在 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 容器。在此之前的 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 也是預覽版,所以本文的操作畫面截圖和程序范例可能會跟將來的正式版有些出入。
練習步驟
步驟 1 :建立項目
開啟 Visual Studio 2015,建立一個新的 ASP.NET Web Application 項目,參考下圖:
項目名稱命名為 DependencyInjectionDemo。按 OK 之后,接着選擇模板「ASP.NET 5 Empty」:
項目建立完成后,大概看一下 Solution Explorer 里面有哪些東西:
根目錄下的 project.json 即是此項目的配置文件,其中包含此項目所依賴的框架與組件。Startup.cs 則會包含應用程序激活時所需執行的初始化工作。
步驟 2 :加入必要組件
項目剛建立完成時的 project.json 內容如下:
{ "webroot": "wwwroot", "version": "1.0.0-*", "exclude": [ "wwwroot" ], "packExclude": [ "**.kproj", "**.user", "**.vspscc" ], "dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta1", }, "frameworks" : { "aspnet50" : { }, "aspnetcore50" : { } } }
我們得在「dependencies」區段中加入 ASP.NET MVC 組件:"Microsoft.AspNet.Mvc": "6.0.0-beta1"(你的開發環境可能是別的版本號)。這些文字都要自己敲進去,不過還好,Visual Studio 有智能提示功能,如下圖:
註:若沒出現智能提示,可按【Alt+右箭頭鍵】令它顯現。
輸入冒號之后,會接着提示版本,如下圖:
雖然移到最底下就能選擇最新的 beta 版本,但它可不一定能在你目前的開發環境上順利運行。保險起見,還是選最上方的 「6.0.0-beta1」。
修改完畢之后,「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 的核心套件。
步驟 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。
步驟 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
若沒出現錯誤訊息,便可繼續下一個步驟。
步驟 5 :撰寫測試用的服務類別
寫一個簡單的類別來作為注入至 controller 的對象。如下所示:
namespace DependencyInjectionDemo { public interface ITimeService { string Now { get; } } public class TimeService : ITimeService { public string Now { get { return DateTime.Now.ToString(); } } } }
代碼很簡單,就不多解釋了。
步驟 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>(); // 加這行! } }
這里是透過 IServiceLocation 的擴展方法 AddScope 來向 ASP.NET 自帶的容器注冊類型對應關系,意思是:碰到需要 ITimeService 對象的時候,使用 TimeService 來建立對象實體。
此外,從方法的名稱大約可以猜得出來,這個 AddScope 方法還有另一個意義,那就是:將來容器在建立 ITimeService 對象時(用 DI 術語來說,就是「解析 ITimeService」),會建立一個「活在特定范圍內」的對象。就此范例而言,這個特定范圍就是一個 HTTP 請求的范圍。
如此一來, 當 MVC 框架在建立 ValuesController 時, 發現它的構造函數需要一個 ITimeService 對象,於是 MVC 框架就會跟自帶容器要一個 ITimeService 對象,然后將此對象傳入 ValuesController 的構造函數。
再執行一次應用程序,這次應該能夠順利執行了。執行結果如下圖:
很簡單吧?這里完全沒用到第三方 DI 框架。
運用相同技巧,你可以將任何對象注入至你的 Controller 類別,例如應用層(application layer)的各類服務/組件。
結語
ASP.NET 5 的自帶 DI 容器支持四種生命周期模式:Instance、Singleton、Transient、Scoped。本文范例中使用的 AddScoped 方法即為 Scoped,或者說 Per Request 生命周期模式,亦即對象只存活在目前請求的范圍內,且同一請求范圍內會共享同一個對象實體。
如欲進一步了解 ASP.NET 5 的 DI 功能與限制,可參考下列文章:
- Dependency Injection in ASP.NET vNext
- Getting started with ASP.NET 5 MVC 6 Web API & Entity Framework
- ASP vNext Dependency Injection Lifecycles