ASP.NET Core使用了大量的依賴(lài)注入(Dependency Injection, DI)宋彼,把控制反轉(zhuǎn)(Inversion Of Control, IoC)運(yùn)用的相當(dāng)巧妙锥累。DI可算是ASP.NET Core最精華的一部分末患,有用過(guò)Autofac或類(lèi)似的DI Framework對(duì)此應(yīng)該不陌生质和。
本篇將介紹ASP.NET Core的依賴(lài)注入(Dependency Injection)柒瓣。
DI 容器介紹
在沒(méi)有使用DI Framework 的情況下喳魏,假設(shè)在UserController 要調(diào)用UserLogic,會(huì)直接在UserController 實(shí)例化UserLogic羡藐,如下:
public class UserLogic {
public void Create(User user) {
// ...
}
}
public class UserController : Controller {
public void Register(User user){
var logic = new UserLogic();
logic.Create(user);
// ...
}
}
以上程序基本沒(méi)什么問(wèn)題,但是依賴(lài)關(guān)系差了點(diǎn)悯许。UserController 必須要依賴(lài)UserLogic才可以運(yùn)行仆嗦,拆出接口改成:
public interface IUserLogic {
void Create(User user);
}
public class UserLogic : IUserLogic {
public void Create(User user) {
// ...
}
}
public class UserController : Controller {
private readonly IUserLogic _userLogic;
public UserController() {
_userLogic = new UserLogic();
}
public void Register(User user){
_userLogic.Create(user);
// ...
}
}
UserController 與UserLogic 的依賴(lài)關(guān)系只是從Action 移到構(gòu)造方法中,依然還是很強(qiáng)的依賴(lài)關(guān)系先壕。
ASP.NET Core透過(guò)DI容器瘩扼,切斷這些依賴(lài)關(guān)系,實(shí)例的產(chǎn)生不在使用方(指上例UserController構(gòu)造方法中的new)垃僚,而是在DI容器集绰。
DI容器的注冊(cè)方式也很簡(jiǎn)單,在Startup.ConfigureServices注冊(cè)谆棺。如下:
Startup.cs
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
}
services就是一個(gè)DI容器栽燕。
此例把MVC的服務(wù)注冊(cè)到DI容器,等到需要用到MVC服務(wù)時(shí)誊锭,才從DI容器取得對(duì)象實(shí)例立镶。
基本上要注入到Service的類(lèi)沒(méi)什么限制味榛,除了靜態(tài)類(lèi)。
以下示例就只是一般的Class繼承Interface:
public interface ISample
{
int Id { get; }
}
public class Sample : ISample
{
private static int _counter;
private int _id;
public Sample()
{
_id = ++_counter;
}
public int Id => _id;
}
要注入的Service需要在Startup.ConfigureServices中注冊(cè)實(shí)例類(lèi)型蔼啦。如下:
Startup.cs
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<ISample, Sample>();
}
}
第一個(gè)泛型為注入的類(lèi)型
建議用Interface來(lái)包裝,這樣在才能把依賴(lài)關(guān)系拆除仰猖。
?第二個(gè)泛型為實(shí)例的類(lèi)型
DI 運(yùn)行方式
ASP.NET Core的DI是采用Constructor Injection捏肢,也就是說(shuō)會(huì)把實(shí)例化的物件從建構(gòu)子傳入。
如果要取用DI容器內(nèi)的物件饥侵,只要在建構(gòu)子加入相對(duì)的Interface即可鸵赫。例如:
Controllers\HomeController.cs
using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;
namespace MyWebsite.Controllers
{
public class HomeController : Controller
{
private readonly ISample _sample;
public HomeController(ISample sample)
{
_sample = sample;
}
public string Index()
{
return $"[ISample]\r\n"
+ $"Id: {_sample.Id}\r\n"
+ $"HashCode: {_sample.GetHashCode()}\r\n"
+ $"Tpye: {_sample.GetType()}";
}
}
}
輸出內(nèi)容如下:
[ISample]
Id: 1
HashCode: 52582687
Tpye: MyWebsite.Models.Sample
ASP.NET Core 實(shí)例化Controller 時(shí),發(fā)現(xiàn)構(gòu)造方法中有ISample 這個(gè)類(lèi)型的參數(shù)爆捞,就把Sample 的實(shí)例注入給該Controller奉瘤。
每個(gè)Request 都會(huì)把Controller 實(shí)例化,所以DI 容器會(huì)從構(gòu)造方法注入ISample 的實(shí)例煮甥,把sample 存到變量_sample 中盗温,就能確保Action 能夠使用到被注入進(jìn)來(lái)的ISample 實(shí)例。
注入實(shí)例過(guò)程成肘,情境如下:
Service 生命周期
注冊(cè)在DI 容器的Service 分三種生命周期:
?Transient
每次注入時(shí)卖局,都重新new一個(gè)新的實(shí)例。
?Scoped
每個(gè)Request都重新new一個(gè)新的實(shí)例双霍,同一個(gè)Request不管經(jīng)過(guò)多少個(gè)Pipeline都是用同一個(gè)實(shí)例砚偶。上例所使用的就是Scoped批销。
?Singleton
被實(shí)例化后就不會(huì)消失,程式運(yùn)行期間只會(huì)有一個(gè)實(shí)例染坯。
小改一下Sample 類(lèi)的代碼:
namespace MyWebsite.Models
{
public interface ISample
{
int Id { get; }
}
public interface ISampleTransient : ISample
{
}
public interface ISampleScoped : ISample
{
}
public interface ISampleSingleton : ISample
{
}
public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
private static int _counter;
private int _id;
public Sample()
{
_id = ++_counter;
}
public int Id => _id;
}
}
在Startup.ConfigureServices中注冊(cè)三種不同生命周期的服務(wù)均芽。如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISampleTransient, Sample>();
services.AddScoped<ISampleScoped, Sample>();
services.AddSingleton<ISampleSingleton, Sample>();
// Singleton 也可以用以下方法注冊(cè)
// services.AddSingleton<ISampleSingleton>(new Sample());
}
}
Service Injection
只要是透過(guò)WebHost產(chǎn)生實(shí)例的類(lèi)型,都可以在構(gòu)造方法注入单鹿。
所以Controller掀宋、View、Filter仲锄、Middleware或自定義的Service等都可以被注入劲妙。
此篇我只用Controller、View儒喊、Service做為范例镣奋。
Controller
在TestController 中注入上例的三個(gè)Services:
Controllers\TestController.cs
using Microsoft.AspNetCore.Mvc;
using MyWebsite.Models;
namespace MyWebsite.Controllers
{
public class TestController : Controller
{
private readonly ISample _transient;
private readonly ISample _scoped;
private readonly ISample _singleton;
public TestController(
ISampleTransient transient,
ISampleScoped scoped,
ISampleSingleton singleton)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
}
public IActionResult Index()
{
ViewBag.TransientId = _transient.Id;
ViewBag.TransientHashCode = _transient.GetHashCode();
ViewBag.ScopedId = _scoped.Id;
ViewBag.ScopedHashCode = _scoped.GetHashCode();
ViewBag.SingletonId = _singleton.Id;
ViewBag.SingletonHashCode = _singleton.GetHashCode();
return View();
}
}
}
Views\Test\Index.cshtml
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
輸出內(nèi)容如下:
從左到又打開(kāi)頁(yè)面三次,可以發(fā)現(xiàn)Singleton的Id及HashCode都是一樣的怀愧,現(xiàn)在還看不太能看出來(lái)Transient及Scoped的差異侨颈。
Service 實(shí)例產(chǎn)生方式:
圖例說(shuō)明:
?A為Singleton對(duì)象實(shí)例
一但實(shí)例化,就會(huì)一直存在于DI容器中掸驱。
?B為Scoped對(duì)象實(shí)例
每次Request就會(huì)產(chǎn)生新的實(shí)例在DI容器中肛搬,讓同Request周期的使用方,拿到同一個(gè)實(shí)例毕贼。
?C為T(mén)ransient對(duì)象實(shí)例
只要跟DI容器請(qǐng)求這個(gè)類(lèi)型温赔,就會(huì)取得新的實(shí)例。
View
View注入Service的方式鬼癣,直接在*.cshtml使用@inject:
Views\Test\Index.cshtml
@using MyWebsite.Models
@inject ISampleTransient transient
@inject ISampleScoped scoped
@inject ISampleSingleton singleton
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
<hr />
<table border="1">
<tr><td colspan="3">View</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>
<tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>
<tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>
</table>
輸出內(nèi)容如下:
從左到又打開(kāi)頁(yè)面三次陶贼,Singleton的Id及HashCode如前例是一樣的。
Transient及Scoped的差異在這次就有明顯差異待秃,Scoped在同一次Request的Id及HashCode都是一樣的拜秧,如紅紫籃框。
Service
簡(jiǎn)單建立一個(gè)CustomService章郁,注入上例三個(gè)Service枉氮,代碼類(lèi)似TestController。如下:
Services\CustomService.cs
using MyWebsite.Models;
namespace MyWebsite.Services
{
public class CustomService
{
public ISample Transient { get; private set; }
public ISample Scoped { get; private set; }
public ISample Singleton { get; private set; }
public CustomService(ISampleTransient transient,
ISampleScoped scoped,
ISampleSingleton singleton)
{
Transient = transient;
Scoped = scoped;
Singleton = singleton;
}
}
}
注冊(cè)CustomService
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddScoped<CustomService, CustomService>();
}
}
第一個(gè)泛型也可以是類(lèi)暖庄,不一定要是接口聊替。
缺點(diǎn)是使用方以Class作為依賴(lài)關(guān)系,變成強(qiáng)關(guān)聯(lián)的依賴(lài)培廓。
在View 注入CustomService:
Views\Home\Index.cshtml
@using MyWebsite
@using MyWebsite.Models
@using MyWebsite.Services
@inject ISampleTransient transient
@inject ISampleScoped scoped
@inject ISampleSingleton singleton
@inject CustomService customService
<table border="1">
<tr><td colspan="3">Cotroller</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@ViewBag.TransientId</td><td>@ViewBag.TransientHashCode</td></tr>
<tr><td>Scoped</td><td>@ViewBag.ScopedId</td><td>@ViewBag.ScopedHashCode</td></tr>
<tr><td>Singleton</td><td>@ViewBag.SingletonId</td><td>@ViewBag.SingletonHashCode</td></tr>
</table>
<hr />
<table border="1">
<tr><td colspan="3">View</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@transient.Id</td><td>@transient.GetHashCode()</td></tr>
<tr><td>Scoped</td><td>@scoped.Id</td><td>@scoped.GetHashCode()</td></tr>
<tr><td>Singleton</td><td>@singleton.Id</td><td>@singleton.GetHashCode()</td></tr>
</table>
<hr />
<table border="1">
<tr><td colspan="3">Custom Service</td></tr>
<tr><td>Lifetimes</td><td>Id</td><td>Hash Code</td></tr>
<tr><td>Transient</td><td>@customService.Transient.Id</td><td>@customService.Transient.GetHashCode()</td></tr>
<tr><td>Scoped</td><td>@customService.Scoped.Id</td><td>@customService.Scoped.GetHashCode()</td></tr>
<tr><td>Singleton</td><td>@customService.Singleton.Id</td><td>@customService.Singleton.GetHashCode()</td></tr>
</table>
輸出內(nèi)容如下:
從左到又打開(kāi)頁(yè)面三次:
?Transient
如預(yù)期惹悄,每次注入都是不一樣的實(shí)例。
?Scoped
在同一個(gè)Requset中肩钠,不論是在哪邊被注入泣港,都是同樣的實(shí)例暂殖。
?Singleton
不管Requset多少次,都會(huì)是同一個(gè)實(shí)例当纱。
參考
Introduction to Dependency Injection in ASP.NET Core
ASP.NET Core Dependency Injection Deep Dive