前言
Dependency Injection 在 ASP.NET Core 中佔著很重要的地位,除了使用 AddSingleton, AddScoped 及 AddTransient 外,還有什麼需要注意的細節以及進階的使用方式呢?
問題研究
問題 1: 如果同一個 interface ,同時加入 2 個不同的 class ,會發生錯誤嗎? 如果沒有發生錯誤的話,在使用時,會取用那一個 class 呢?
1.1. TimeRandomService 及 GeneratorRandomService 分別實作 IRandomService
1 |
|
1.2. 在 Startup.cs 中的 ConfigureServices Method 中,我們加入註冊 IRandomService 的部份
1 |
|
1.3. 在 services.AddTransient 後加入中斷點,可以發現,針對同一個 interface 加入不同的實作 class ,並不會引發錯誤,而且會依程式碼的順序加入到 services 之中,如下圖所示,
1.4. 在 Razor Page 中注入 IRandomService ,並觀察會使用那一個實作的 Class
1 |
|
1 |
|
1.5. 從結果可以發現,它會取用最後註冊的那個實作 Class,也就是 GeneratorRandomService ,如下圖所示,
所以當我們在 Startup.cs 中的 ConfigureServices Method 中,如果最後加入的是 TimeRandomService ,就會取用 TimeRandomService 這個實作的 Class 哦。
這有什麼好處呢? 好處是它允許後者有機會去替換掉原本的實作 Class 。
問題 2: 承問題 1,那有沒有方式是沒有註冊時才允許註冊呢?
2.1. 可以使用 TryAddSingleton, TryAddScoped 及 TryAddTransient
1 |
|
2.2. 使用 TryAddSingleton, TryAddScoped 及 TryAddTransient 後,如果已有註冊過的資料,便不再會被加進去 services 之中,如下圖,
問題 3: TryAdd 是存在就不加,那有方式可以 Replace 已註冊的嗎?
3.2. 可以使用 IServiceCollection.Replace
1 |
|
註:
如果 services 中沒有先註冊 IRandomService ,使用 services.Replace 不會噴錯,會直接註冊進去,像是 CreateOrReplace 。
如果 services 中註冊了 2 個以上 IRandomService ,使用 services.Replace 只會 Replace 第一個註冊的實作類別,並移到後面。
如果要整個移除的話,可以使用 IServiceCollection.RemoveAll ,例如 services.RemoveAll
問題 4: 那針對同一個 interface 註冊那麼多個實作的 Class ,有什麼時機點會這樣子用呢?
4.1. 像系統中常常有一些 規則檢查、 訊息多通道的通知 等等,都需要多個實作的 Class 。
4.2. DateLengthRule 及 EndDateRule 分別實作 IRule
1 |
|
4.3. 在 Startup.cs 中的 ConfigureServices Method 中,我們加入註冊 IRule 的部份
1 |
|
註:
也可以使用 IServiceCollection.TryAddEnumerable 一次加入多個
4.4. 在 Razor Page 中注入 IEnumerable
1 |
|
4.5. 在 Razor Page View 來呈現檢查的資訊
1 |
|
問題 5: 上面那個 MaxHours 是否可以從 config 中讀取呢?
5.1. 在 appsettings.json 中加入設定值 (RuleConfig)
1 | { |
5.2. 建立 Config 物件
1 |
|
5.3. 設定 RuleConfig
1 |
|
5.4. 在 DateLengthRule Class 就可以使用 RuleConfig 來取得 Config 中的值,如下,
1 |
|
5.5. 在 DateLengthRule Class 取得 Config 中的值,如下,
問題 6: RuleConfig 改成註冊 interface 嗎?
6.1. 定義 IRuleConfig, RuleConfig 實作它
1 |
|
6.2. 在 Startup.cs 中的 ConfigureServices Method 中,多加入註冊 IRuleConfig 的部份
1 |
|
6.3. 在 DateLengthRule Class 就可以透過 IRuleConfig 來取得 Config 中的值,如下,
1 |
|
問題 7: 通常系統中會有很多的 Config ,這時在 Startup.cs 的 ConfigureServices 中不就會有一堆的 service.Add …?
7.1. 可以依目標將這些抽到 Extension Methods 之中,例如我們建立一個 AddAppConfiguration Method,將原本在 Startup.cs 中的程式碼搬進來
1 |
|
7.2. Startup.cs 就改呼叫 AddAppConfiguration ,就會比較簡潔
1 |
|
問題 8: 像有一些 泛型類別 的註冊,需要依每個 類別註冊嗎?
8.1. 例如課程中有定義 IDistributedCache 及 實作類別 DistributedCache ,
1 |
|
8.2. 在 Startup.cs 的 ConfigureServices 中,需要針對使用的去註冊
1 |
|
不過這樣子,每要用到一個,就要註冊一個,很容易會忘記,不好維護。
8.3. 針對這種 泛型類別 的註冊,也可以使用 泛型 的方式 typeof(IDistributedCache<>) 來註冊
1 |
|
問題 9: 如果在 Page or Controller 中,只有某個 Method 會使用到某 Service ,可以只在那個 Method 注入服務嗎?
9.1. 可以在 Method 的參數加上 [FromServices] ,
1 |
|
問題 10: Middleware 可以使用嗎?
10.1. 建立一個 middleware 來測試一下
1 |
|
10.2. 在 Startup.cs 的 ConfigureServices 中,註冊 IRandomService ,範圍為 Scoped
1 |
|
10.3. 在 Startup.cs 的 Configure 中,註冊使用 LastRequestMiddleware
1 |
|
10.4. 執行起來後,就會發生錯誤
Cannot resolve scoped service ‘xxx.IRandomService’ from root provider.
這是因為 Middleware 是 Singleton 的範例,而引用到了 scoped 範圍的服務
10.5. 解法 1:將 IRandomService 改成 Singleton 範圍提供服務
1 |
|
10.6. 解法 2:改從 InvokeAsync 注入 IRandomService 服務
1 |
|
問題 11: View 要如何注入呢?
11.1. 使用 @inject
1 |
|
問題 12: 自行建立 Scope 取得 Service
12.1. 這種通常是在啟動系統時,做一些初始化的工作,使用 IHost.Services.CreateScope() ,例如,
1 |
|
問題 13: 前面 IRule ,是否能自動註冊實作它的 Class 呢?
13.1. 可以透過 Scrutor 來達到,請先加入 Scrutor Nuget 套件
13.2. 使用 services.Scan 取代原本的 services.TryAddEnumerable
1 |
|
FromAssemblyOf
: 取 Startup 所在的那個 Assembly
AddClasses : 加入那些有實作 IRule 的 Class
AsImplementedInterfaces : 註冊那些 Class 實作的 Interface
WithScopedLifetime : 註冊為 Scoped 生命範圍
問題 14: 使用 Scan 也可以 註冊成不同的生命範圍嗎?
14.1. 這可以透過 interface 來做一些手腳
1 |
|
14.2. DateLengthRule 從實作 IRule 改成實作 IScopedRule, EndDateRule 則改實作 ISingletonRule
1 |
|
14.3. 在 Startup.cs 的 ConfigureServices 中,就可以分別掃出 ISingletonRule 及 IScopedRule 設定它的生命範圍
1 |
|
如果要掃多個組件,可以使用 FromAssemblies
1 | services.Scan(scan => |
以上以問題的方式來呈現,希望可以讓大家更了解 ASP.NET Core DI 的使用方式。
註:
本文依 Dependency Injection in ASP.NET Core - pluralsight 課程整理,非常建議大家觀看該課程。
參考資料
Dependency Injection in ASP.NET Core - pluralsight
Dependency injection in ASP.NET Core - docs
It depends: Loving .NET Core dependency injection or not
ASP.NET Core Dependency Injection
用 Scrutor 来简化 ASP.NET Core 的 DI 注册
Register types automatically using Scrutor library for ASP.NET Core