最近在 Code Review 時主管提到關於 ASP.NET Core 的一些基本觀念,發現自己對於 .Core DI Service lifetime 部分有些模糊,在 .NET Core 中整個框架建立在 DI ( Dependency Injection) 之上,到處都可以看到 DI 的影子,因此決定寫篇文章釐清自己的觀念,若有問題或是有更推薦的方法歡迎提出一起討論或是給予指導。
.NET Core DI
.NET Core 有提供內建 DI Framework,命名空間為 Microsoft.Extensions.DependencyInjection ,使用 DI 容器服務第一步需要先做註冊,在 .NET Core DI 是在 startup.cs 中的 ConfigureServices 使用 IServiceCollection 的擴充方法指定服務容器生命週期 (LifeTime),預設提供的擴充方法與說明為
- Transient : 每次請求時都會產生新的 Instance
- Scoped : 每個 http Request 都會產生一份 Instance
- Singleton : 整個 Application 只會有一份 Instance
首先,先建立.NET Core Console 專案,專案建立完畢後新增一個 interface 為 IOperation,其屬性 OperationId 為 guid 型別為唯一識別碼,以及其他不同生命週期的 interface 分別實作基本 IOperation 介面
public interface IOperation { Guid OperationId { get; } } public interface IOperationTransient : IOperation{} public interface IOperationScoped : IOperation{} public interface IOperationSingleton : IOperation{} public interface IOperationSingletonInstance : IOperation{}接著新增 Operation 類別並實作不同生命週期的介面,並定義如果未提供 guid 時自己產生一份新的 guid
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance { public Operation() : this(Guid.NewGuid()) { } public Operation(Guid id) { OperationId = id; } public Guid OperationId { get; private set; } }接下來我們在 program.cs main 方法新增 ServiceCollection instance 並註冊 IOperationScoped、IOperationTransient、IOperationSingleton 到對應的 AddScoped、AddTransient、AddSingleton 生命週期中,接著透過 service.BuildServiceProvider 方法新增 scope
using Microsoft.Extensions.DependencyInjection; using System; namespace EFCoreConsoleApp { class Program { static void Main(string[] args) { var services = new ServiceCollection() .AddScoped<IOperationScoped, Operation>() .AddTransient<IOperationTransient, Operation>() .AddSingleton<IOperationSingleton, Operation>(); var provider = services.BuildServiceProvider(); using (var scope1 = provider.CreateScope()) { var p = scope1.ServiceProvider; Console.WriteLine("<SCOPE 1>"); Console.WriteLine("---Test 1---"); Console.WriteLine($"transient1: {p.GetService<IOperationTransient>().OperationId}"); Console.WriteLine($"scope1 : { p.GetService<IOperationScoped>().OperationId }"); Console.WriteLine($"singleton1: {p.GetService<IOperationSingleton>().OperationId}"); Console.WriteLine(" "); Console.WriteLine("---Test 2---"); Console.WriteLine($"transient2: {p.GetService<IOperationTransient>().OperationId}"); Console.WriteLine($"scope2 : { p.GetService<IOperationScoped>().OperationId }"); Console.WriteLine($"singleton2: {p.GetService<IOperationSingleton>().OperationId}"); Console.WriteLine(" "); } using (var scope2 = provider.CreateScope()) { var p = scope2.ServiceProvider; Console.WriteLine("<SCOPE 2>"); Console.WriteLine("---Test 3---"); Console.WriteLine($"transient3: {p.GetService<IOperationTransient>().OperationId}"); Console.WriteLine($"scope3 : { p.GetService<IOperationScoped>().OperationId }"); Console.WriteLine($"singleton3: {p.GetService<IOperationSingleton>().OperationId}"); } Console.ReadKey(); } } }
輸出如下
<SCOPE 1> ---Test 1--- transient1: ee48347a-a819-402b-8e4b-2d0e6a480186 scope1 : b7d9f907-6f95-4cbb-8ccb-3a2fb247287a singleton1: 0c92f569-2b89-4be9-a57c-5d0fc2dd8779 ---Test 2--- transient2: 03a58729-7efe-4394-a9ae-0197e4a14e5c scope2 : b7d9f907-6f95-4cbb-8ccb-3a2fb247287a singleton2: 0c92f569-2b89-4be9-a57c-5d0fc2dd8779 <SCOPE 2> ---Test 3--- transient3: 7c7334af-62e4-4085-b7a5-9876865412c1 scope3 : fe99001b-3566-4850-bc17-6757e7051eeb singleton3: 0c92f569-2b89-4be9-a57c-5d0fc2dd8779透過以上測試與觀察輸出結果,可以得到以下結論
- Transient : 每次請求時都會產生新的 Instance > 每次都不一樣
- Scoped : 每個 http Request 都會產生一份 Instance > 相同 Scoped 時會相同,不同 Scoped 時會不一樣
- Singleton : 整個 Application 只會有一份 Instance > 整個應用程式一份
使用情境
簡單來說,可以依據情境做不同的生命週期註冊的動作,舉例當 EF Core 中的 DBContext 當未設定生命週期時預設就會是 Scoped 使用,可以跨 Service 不用重複 new instance;如果今天是與 Redis 建立單一連線情境,避免建立過多連線造成 Timeout,這時候就是用到 Singletion 設定;如果是每次 request 都是獨一的就可以使用 transient,當你工作上有需要都可以思考看看哪種生命週期最適合。
參考
.NET Core 中的相依性插入
全面理解 ASP.NET Core 依赖注入
0 意見:
張貼留言