在 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 也是预览版,所以本文的操作画面截图和程序范例可能会跟将来的正式版有些出入。
开启 Visual Studio 2015,建立一个新的 ASP.NET Web Application 项目,参考下图:
项目名称命名为 DependencyInjectionDemo。按 OK 之后,接着选择模板「ASP.NET 5 Empty」:
项目建立完成后,大概看一下 Solution Explorer 里面有哪些东西:
根目录下的 project.json 即是此项目的配置文件,其中包含此项目所依赖的框架与组件。Startup.cs 则会包含应用程序激活时所需执行的初始化工作。
项目刚建立完成时的 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" },
说明:
开启 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(); } } }
说明:
註:IServiceCollection 接口隶属于命名空间 Microsoft.Framework.DependencyInjection。
在 Solution Explorer 中,项目的根目录下建立一个文件夹:Controllers。然后在此文件夹上点右键,选 Add > New Item。在新开启的对话窗中选择「Web API Controller Class」,并将档案命名为 ValuesController.cs。参考下图:
DBAEAF227.png" alt="" />
产生的代码大致如下:
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
若没出现错误讯息,便可继续下一个步骤。
写一个简单的类别来作为注入至 controller 的对象。如下所示:
namespace DependencyInjectionDemo { public interface ITimeService { string Now { get; } } public class TimeService : ITimeService { public string Now { get { return DateTime.Now.ToString(); } } } }
代码很简单,就不多解释了。
修改 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 功能与限制,可参考下列文章: