前言
最近在 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),預設提供的擴充方法與說明為
首先,先建立.NET Core Console 專案,專案建立完畢後新增一個 interface 為 IOperation,其屬性 OperationId 為 guid 型別為唯一識別碼,以及其他不同生命週期的 interface 分別實作基本 IOperation 介面
示意圖
使用情境
簡單來說,可以依據情境做不同的生命週期註冊的動作,舉例當 EF Core 中的 DBContext 當未設定生命週期時預設就會是 Scoped 使用,可以跨 Service 不用重複 new instance;如果今天是與 Redis 建立單一連線情境,避免建立過多連線造成 Timeout,這時候就是用到 Singletion 設定;如果是每次 request 都是獨一的就可以使用 transient,當你工作上有需要都可以思考看看哪種生命週期最適合。
參考
.NET Core 中的相依性插入
全面理解 ASP.NET Core 依赖注入
最近在 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 方法新增 scopeusing 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 依赖注入

