只有累積,沒有奇蹟

2019年4月6日 星期六

[.NETCore] ASP.NET Core DI 生命週期 LifeTime

前言
最近在 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 介面
  1. public interface IOperation
  2. {
  3. Guid OperationId { get; }
  4. }
  5.  
  6. public interface IOperationTransient : IOperation{}
  7.  
  8. public interface IOperationScoped : IOperation{}
  9.  
  10. public interface IOperationSingleton : IOperation{}
  11.  
  12. public interface IOperationSingletonInstance : IOperation{}
接著新增 Operation 類別並實作不同生命週期的介面,並定義如果未提供 guid 時自己產生一份新的 guid 
  1. public class Operation : IOperationTransient,
  2. IOperationScoped,
  3. IOperationSingleton,
  4. IOperationSingletonInstance
  5. {
  6. public Operation() : this(Guid.NewGuid())
  7. {
  8. }
  9.  
  10. public Operation(Guid id)
  11. {
  12. OperationId = id;
  13. }
  14.  
  15. public Guid OperationId { get; private set; }
  16. }
接下來我們在 program.cs main 方法新增 ServiceCollection instance 並註冊 IOperationScoped、IOperationTransient、IOperationSingleton 到對應的 AddScoped、AddTransient、AddSingleton 生命週期中,接著透過 service.BuildServiceProvider 方法新增 scope
  1. using Microsoft.Extensions.DependencyInjection;
  2. using System;
  3.  
  4. namespace EFCoreConsoleApp
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. var services = new ServiceCollection()
  11. .AddScoped<IOperationScoped, Operation>()
  12. .AddTransient<IOperationTransient, Operation>()
  13. .AddSingleton<IOperationSingleton, Operation>();
  14.  
  15. var provider = services.BuildServiceProvider();
  16. using (var scope1 = provider.CreateScope())
  17. {
  18. var p = scope1.ServiceProvider;
  19. Console.WriteLine("<SCOPE 1>");
  20. Console.WriteLine("---Test 1---");
  21. Console.WriteLine($"transient1: {p.GetService<IOperationTransient>().OperationId}");
  22. Console.WriteLine($"scope1 : { p.GetService<IOperationScoped>().OperationId }");
  23. Console.WriteLine($"singleton1: {p.GetService<IOperationSingleton>().OperationId}");
  24. Console.WriteLine(" ");
  25. Console.WriteLine("---Test 2---");
  26. Console.WriteLine($"transient2: {p.GetService<IOperationTransient>().OperationId}");
  27. Console.WriteLine($"scope2 : { p.GetService<IOperationScoped>().OperationId }");
  28. Console.WriteLine($"singleton2: {p.GetService<IOperationSingleton>().OperationId}");
  29. Console.WriteLine(" ");
  30. }
  31.  
  32. using (var scope2 = provider.CreateScope())
  33. {
  34. var p = scope2.ServiceProvider;
  35. Console.WriteLine("<SCOPE 2>");
  36. Console.WriteLine("---Test 3---");
  37. Console.WriteLine($"transient3: {p.GetService<IOperationTransient>().OperationId}");
  38. Console.WriteLine($"scope3 : { p.GetService<IOperationScoped>().OperationId }");
  39. Console.WriteLine($"singleton3: {p.GetService<IOperationSingleton>().OperationId}");
  40. }
  41.  
  42. Console.ReadKey();
  43. }
  44. }
  45. }
  46.  
輸出如下
  1. <SCOPE 1>
  2. ---Test 1---
  3. transient1: ee48347a-a819-402b-8e4b-2d0e6a480186
  4. scope1 : b7d9f907-6f95-4cbb-8ccb-3a2fb247287a
  5. singleton1: 0c92f569-2b89-4be9-a57c-5d0fc2dd8779
  6.  
  7. ---Test 2---
  8. transient2: 03a58729-7efe-4394-a9ae-0197e4a14e5c
  9. scope2 : b7d9f907-6f95-4cbb-8ccb-3a2fb247287a
  10. singleton2: 0c92f569-2b89-4be9-a57c-5d0fc2dd8779
  11.  
  12. <SCOPE 2>
  13. ---Test 3---
  14. transient3: 7c7334af-62e4-4085-b7a5-9876865412c1
  15. scope3 : fe99001b-3566-4850-bc17-6757e7051eeb
  16. 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 依赖注入

Related Posts:

  • [NETCore] ASP.NET Core 3.0 Worker Service 搭配 Coravel 建立排程服務前言 如果一直有在 follow 消息的朋友可以發現在 ASP.NET 3.0 有新增 Work Services 專案範本,可以透過幾個簡單的步驟使用 Workers with Windows Services 服務,詳細可以參考微軟官網對於 worker Service 的介紹文章 .NET Core Workers as Windows Services,在上一篇介紹了 ASP.NET Core 中的輕量級排程… Read More
  • [NETCore] ASP.NET Core 建立與解析 QueryString 參數說明 之前介紹過在 .NET 中可以使用 Utility.ParseQueryString 處理 Url 中的參數,傳送門 : 使用 ParseQueryString 取得網址參數,但所使用的 System.Web 命名空間僅存在於 ASP.NET Framework 不支援 ASP.NET Core,在搜尋更好的解決方案中發現了在 ASP.NET Core 提供新的 API - QueryHelpers 可以達到同樣效果,此… Read More
  • [NETCore] ASP.NET Core 中的排程利器 - Coravel 前言 在 ASP.NET 中相信大家都會有過開發 Scheduler 排程的需求,過去可能會使用 ASP.NET 中較有名的框架像是 Quartz.NET 或是 Hangfire,兩種框架各有優缺點小弟不才剛好都有碰過,兩種排程框架各有喜好者可以依據自己的愛好來選用。今天所要介紹的是另一套 Schedule Job 框架 Coravel,作者在設計 API 時用 Fluent interface 方式進行設計,因此在使用上相當直覺與方便… Read More
  • [NETCore] ASP.NET Core 啟動失敗 - 嘗試存取通訊端被拒絕,因為存取權限不足問題  接獲同事詢問專案無法正常啟用,專案是使用 ASP.NET Core 2.2 開發並搭配 Kestrel 使用,在過去開發時都正常運作但今天忽然就遭遇啟動異常的狀況,在啟用時會跳出錯誤訊息為 'Unable to bind to http://localhost:5000 on the IPv4 loopback interface: '嘗試存取通訊端被拒絕,因為存取權限不足。''  ,這篇… Read More
  • [NETCore] 如何在 ASP.NET Core 中使用 YAML前言 今天同事在分享 ASP.NET Core 時介紹使用 YAML 作為組態設定檔,自己在過去比較少接觸 YAML 設定檔,也趁此機會來好好讓彼此熟悉一下,在 YAML 官網介紹說明文字如下 YAML is a human friendly data serialization standard for all programming languages. YAML 是一種可讀性高,適用於表達資料序列化的格式,在公司 CI… Read More

0 意見:

張貼留言

Copyright © 2025 m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com