ASP.NET Core 教學 - Dependency Injection

-- Pageviews

ASP.NET Core 教學 - Dependency Injection - 運作方式

ASP.NET Core 使用了大量的 DI (Dependency Injection) 設計,有用過 Autofac 或類似的 DI Framework 對此應該不陌生。
本篇將介紹 ASP.NET Core 的 Dependency Injection。

DI 運作方式

ASP.NET Core 的 DI 是採用 Constructor Injection,也就是說會把實例化的物件從建構子傳入。例如:

1
2
3
4
5
6
7
8
9
10
public class HomeController : Controller
{
private readonly ISample _sample;

public HomeController(ISample sample)
{
_sample = sample;
// ...
}
}

上述的 sample,會在 HomeController 被實例化的時候注入進來。
每個 Request 都會把 Controller 實例化,所以從建構子注入,就能確保 Action 能夠使用到被注入進來的 _sample。

光看上面的程式碼,可能會很困惑 ASP.NET Core 要如何知道 sample 的實做類別?
要注入的 Service 需要在 Startup 中註冊實做類別。如下:

1
2
3
4
5
6
7
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ISample, Sample>();
}
}

如此一來,在 ASP.NET Core 實例化 Controller 時,發現建構子有 ISample 這個類型的參數,就把 Sample 的實例注入給該 Controller。

1. 建立 Service

基本上要注入到 Service 的類別沒什麼限制,除了靜態類別。
以下範例程式就只是一般的 class 繼承 interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface ISample
{
Guid Id { get; }
}

public interface ISampleTransient : ISample
{
}

public interface ISampleScoped : ISample
{
}

public interface ISampleSingleton : ISample
{
}

public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
private Guid _id;

public Sample()
{
_id = Guid.NewGuid();
}

public Guid Id => _id;
}

2. 註冊 Service

註冊 Service 有分三種方式:

  1. Transient
    每次注入時,都重新 new 一個新的實體。
  2. Scoped
    每個 Request 都重新 new 一個新的實體。
  3. Singleton
    程式啟動後會 new 一個實體。也就是運行期間只會有一個實體。
1
2
3
4
5
6
7
8
9
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISampleTransient, Sample>();
services.AddScoped<ISampleScoped, Sample>();
services.AddSingleton<ISampleSingleton, Sample>();
}
}

第一個泛型為注入的類型,建議用 interface 來包裝,這樣在才能把相依關係拆除。
第二個泛型為實做的類別。

Service 實例產生方式: ASP.NET Core 教學 - Dependency Injection - 運作方式動畫

  • A 為 Singleton
  • B 為 Scoped
  • C 為 Transient

3. 注入 Service

被注入的 Service 可以在 Controller、View、Filter、Middleware 或自訂的 Service 等使用,只要是透過 ASP.NET Core 產生實例的類別,都可以在建構子定義型態注入。
此篇我只用 Controller、Service、View 做為範例。

3.1. Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HomeController : Controller
{
public readonly ISampleTransient _sampleTransient;
public readonly ISampleScoped _sampleScoped;
public readonly ISampleSingleton _sampleSingleton;

public HomeController(ISampleTransient sampleTransient, ISampleScoped sampleScoped, ISampleSingleton sampleSingleton)
{
_sampleTransient = sampleTransient;
_sampleScoped = sampleScoped;
_sampleSingleton = sampleSingleton;
}

public IActionResult Index()
{
var message = $"<tr><td>Transient</td><td>{_sampleTransient.Id}</td></tr>"
+ $"<tr><td>Scoped</td><td>{_sampleScoped.Id}</td></tr>"
+ $"<tr><td>Singleton</td><td>{_sampleSingleton.Id}</td></tr>";
return View(model: message);
}
}

3.2. Service

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SampleService
{
public ISampleTransient SampleTransient { get; private set; }
public ISampleScoped SampleScoped { get; private set; }
public ISampleSingleton SampleSingleton { get; private set; }

public SampleService(ISampleTransient sampleTransient, ISampleScoped sampleScoped, ISampleSingleton sampleSingleton)
{
SampleTransient = sampleTransient;
SampleScoped = sampleScoped;
SampleSingleton = sampleSingleton;
}
}

註冊 SampleService

1
2
3
4
5
6
7
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<SampleService, SampleService>();
}
}

第一個泛型也可以是類別,不一定要是介面。

3.3. View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@model  string

@using MyWebsite

@inject ISampleTransient sampleTransient
@inject ISampleScoped sampleScoped
@inject ISampleSingleton sampleSingleton
@inject SampleService sampleService

<table>
<colgroup>
<col width="100" />
</colgroup>
<tbody>
<tr><th colspan="2">Controler</th></tr>
@Html.Raw(Model)

<tr><th colspan="2">Service</th></tr>
<tr><td>Transient</td><td>@sampleService.SampleTransient.Id</td></tr>
<tr><td>Scoped</td><td>@sampleService.SampleScoped.Id</td></tr>
<tr><td>Singleton</td><td>@sampleService.SampleSingleton.Id</td></tr>

<tr><th colspan="2">View</th></tr>
<tr><td>Transient</td><td>@sampleTransient.Id</td></tr>
<tr><td>Scoped</td><td>@sampleScoped.Id</td></tr>
<tr><td>Singleton</td><td>@sampleSingleton.Id</td></tr>
</tbody>
</table>

執行結果

  1. Transient 如預期,每次都不一樣。
  2. Scoped 在同一個 Requset 中,不論是在哪邊被注入,都是同樣的實體。(紅色箭頭)
  3. Singleton 不管 Requset 多少次,都會是同一個實體。(藍色方框)

ASP.NET Core 教學 - Dependency Injection - 範例執行結果

程式碼下載

asp-net-core-dependency-injection

參考

ASP.NET Core Dependency Injection Deep Dive
Introduction to Dependency Injection in ASP.NET Core