Asp.Net Core中服務的生命周期選項區別和用法


  在做一個小的Demo中,在一個界面上兩次調用視圖組件,並且在視圖組件中都調用了數據庫查詢,結果發現,一直報錯,將兩個視圖組件的調用分離,單獨進行,卻又是正常的,尋找一番,發現是配置依賴注入服務時,對於服務的生命周期沒有配置得當導致,特此做一次實驗來認識三者之間(甚至是四者之間的用法及區別)。

  本文demo地址(具體見WebApi控制器中):https://gitee.com/530521314/Partner.TreasureChest/tree/master/ServiceLifetime

 

一、服務的生命周期

  在Asp.Net Core中,內置容器負責管理服務的生命周期,從被依賴注入容器創建開始,等我們調用完服務時,到容器釋放該服務的所有實力為止,有幾種形式表現:

  1、Transient:每次請求服務時,都會創建一個新實例,這種生命周期適合用於輕量級服務(如Repository和ApplicationService服務)。

  2、Scoped:為每個HTTP請求創建一個實例,生命周期將橫貫整次請求。

  3、SingleTon:在第一次請求服務時,為該服務創建一個實例,之后每次請求將會使用第一次創建好的服務。

  4、Instance:與SingleTon類似,但在應用程序啟動時會將該實例注冊到容器中,可以理解為比SingleTon還早存在。

  應用程序中相關服務的控制生命周期的方法時通過相應的Add*指定,如下三種,當然還可以通過擴展方法來簡化ConfigurationServices方法中所見的代碼數量。

services.AddTransient<IApplicationService, ApplicationService>();
services.AddScoped<IApplicationService, ApplicationService>();
services.AddSingleton<IApplicationService, ApplicationService>();

 

二、代碼設計服務生命周期

  首先設計一些服務相關的操作接口

 1     public interface IOperation
 2     {
 3         Guid GetGuid();
 4     }
 5 
 6     public interface IOperationTransient: IOperation
 7     {
 8 
 9     }
10 
11     public interface IOperationScoped : IOperation
12     {
13 
14     }
15 
16     public interface IOperationSingleton : IOperation
17     {
18       
19     }
20 
21     public interface IOperationInstance : IOperation
22     {
23    
24     }
基礎服務接口

  其次對這些操作類予以實現並生成相關服務

  1     /// <summary>
  2     /// 常規服務
  3     /// </summary>
  4     public class Operation : IOperation
  5     {
  6         private readonly Guid _guid;
  7 
  8         public Operation()
  9         {
 10             _guid = Guid.NewGuid();
 11         }
 12 
 13         public Operation(Guid guid)
 14         {
 15             _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
 16         }
 17 
 18         public Guid GetGuid()
 19         {
 20             return _guid;
 21         }
 22     }
 23 
 24     /// <summary>
 25     /// 瞬時服務
 26     /// </summary>
 27     public class OperationTransient : IOperationTransient
 28     {
 29         private readonly Guid _guid;
 30 
 31         public OperationTransient()
 32         {
 33             _guid = Guid.NewGuid();
 34         }
 35 
 36         public OperationTransient(Guid guid)
 37         {
 38             _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
 39         }
 40 
 41         public Guid GetGuid()
 42         {
 43             return _guid;
 44         }
 45     }
 46 
 47     /// <summary>
 48     /// 單次請求內服務固定
 49     /// </summary>
 50     public class OperationScoped : IOperationScoped
 51     {
 52         private readonly Guid _guid;
 53 
 54         public OperationScoped()
 55         {
 56             _guid = Guid.NewGuid();
 57         }
 58 
 59         public OperationScoped(Guid guid)
 60         {
 61             _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
 62         }
 63 
 64         public Guid GetGuid()
 65         {
 66             return _guid;
 67         }
 68     }
 69 
 70 
 71     /// <summary>
 72     /// 所有請求內固定服務
 73     /// </summary>
 74     public class OperationSingleton : IOperationSingleton
 75     {
 76         private readonly Guid _guid;
 77 
 78         public OperationSingleton()
 79         {
 80             _guid = Guid.NewGuid();
 81         }
 82 
 83         public OperationSingleton(Guid guid)
 84         {
 85             _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
 86         }
 87 
 88         public Guid GetGuid()
 89         {
 90             return _guid;
 91         }
 92     }
 93 
 94     /// <summary>
 95     /// 應用程序內固定服務
 96     /// </summary>
 97     public class OperationInstance : IOperationInstance
 98     {
 99         private readonly Guid _guid;
100 
101         public OperationInstance()
102         {
103             _guid = Guid.NewGuid();
104         }
105 
106         public OperationInstance(Guid guid)
107         {
108             _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
109         }
110 
111         public Guid GetGuid()
112         {
113             return _guid;
114         }
115     }
基礎服務具體實現

  對基礎服務的聚合接口,提供統一服務接口

    public interface IOperationService
    {
        /// <summary>
        /// 獲取四種形式的Guid碼
        /// </summary>
        /// <returns></returns>
        List<string> GetGuidString();
    }
聚合服務接口

  對基礎服務的聚合實現,將基礎服務全部接入進來作為統一服務

 1     /// <summary>
 2     /// 服務調用
 3     /// </summary>
 4     public class OperationService : IOperationService
 5     {
 6         public IOperationTransient _transientOperation { get; }
 7         public IOperationScoped _scopedOperation { get; }
 8         public IOperationSingleton _singletonOperation { get; }
 9         public IOperationInstance _instanceOperation { get; }
10 
11         public OperationService(IOperationTransient transientOperation,
12             IOperationScoped scopedOperation,
13             IOperationSingleton singletonOperation,
14             IOperationInstance instanceOperation)
15         {
16             _transientOperation = transientOperation;
17             _scopedOperation = scopedOperation;
18             _singletonOperation = singletonOperation;
19             _instanceOperation = instanceOperation;
20         }
21 
22         public List<string> GetGuidString()
23         {
24             return new List<string>()
25             {
26                 $"Transient:"+_transientOperation.GetGuid(),
27                 $"Scoped:"+_scopedOperation.GetGuid(),
28                 $"Singleton:" +_singletonOperation.GetGuid(),
29                 $"Instance:"+_instanceOperation.GetGuid(),
30             };
31         }
32     }
聚合服務的實現

  在控制器中進行服務注入

 1     [Route("api/[controller]")]
 2     [ApiController]
 3     public class ValuesController : ControllerBase
 4     {
 5         private readonly IOperationService _operationService;
 6 
 7         public ValuesController(IOperationService operationService)
 8         {
 9             _operationService = operationService;
10         }
11 
12         [HttpGet]
13         [Route(nameof(GetGuidString))]
14         public ActionResult<string> GetGuidString()
15         {
16             return string.Join("\n", _operationService.GetGuidString());
17         }
18     }

  在StartUp中完成服務注入邏輯,這里實現服務注入的方式多種均可。

services.AddTransient<IOperationTransient, OperationTransient>();
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddSingleton<IOperationSingleton, OperationSingleton>();
//應用程序啟動時便注入該實例 services.AddSingleton
<IOperationInstance>(new OperationInstance(Guid.Empty)); services.AddTransient<IOperationService, OperationService>();

  通過訪問預期Api地址可以得到不同的四種基礎服務的Guid信息,

  第一次啟動程序(不關閉)發起訪問:

  

  第二次(第一次基礎上再次訪問)發起訪問:

  

  可以看見,兩次訪問下,Singleton和Instance是相同的,都是由應用程序啟動時和應用服務加載時決定完畢,Singleton在首次進入服務時進行分配,並始終保持不變,而Instance在應用程序啟動時,便將實例注入,進入服務也保持着最先的實例,沒有重新分配實例。而Transient和Scoped則進行着變化。

  關閉程序,重啟,第三次發起訪問:

  

  可以見到,Singleton和Instance都發生了變化,也說明了之前在Singleton和Instance處寫上的作用。

  接下來開始設計Transient和Scoped的不同之處,對於已有代碼加上新功能,此次我們只針對Scoped和Transient進行比較。

  首先在StartUp中將HttpContextAccessor服務注入,目的是在后期能夠針對Scoped獲取新的服務實例(盡管兩個實例是相同的)。

   services.AddHttpContextAccessor();

  接着在聚合服務中增加一個方法,用來針對Transient、Scoped測試。

1     /// <summary>
2     /// 獲取Transient、Scoped的Guid碼
3     /// </summary>
4     /// <returns></returns>
5     List<string> GetTransientAndScopedGuidString();

  在聚合服務實現中實現該方法並對已有的服務重新獲取實例,得到不同實例下的Guid碼。

 1     public List<string> GetTransientAndScopedGuidString()
 2     {
 3         //var tempTransientService = (IOperationTransient)ServiceLocator.Instance.GetService(typeof(IOperationTransient));
 4 
 5         var tempTransientService = (IOperationTransient)_httpContextAccessor.HttpContext.RequestServices.GetService(typeof(IOperationTransient));
 6         var tempScopedService = (IOperationScoped)_httpContextAccessor.HttpContext.RequestServices.GetService(typeof(IOperationScoped));
 7 
 8         return new List<string>()
 9         {
10             $"原生Transient請求服務:"+_transientOperation.GetGuid(),
11             $"手動Transient請求服務:"+ tempTransientService.GetGuid(),
12             $"原生Scoped請求服務:"+_scopedOperation.GetGuid(),
13             $"手動Scoped請求服務:"+tempScopedService.GetGuid(),
14         };
15     }

  在控制器部分調用該聚合服務即可,並返回相應的結果,本次我返回的結果:

  

  可以看到,對於Scoped來講,一次請求內多次訪問同一個服務是共用一個服務實例的,而對於Transient則是,每次訪問都是新的服務實例。

  至此,對於這四種服務生命周期算是掌握的差不多了。

 

  參考:

     蔣老師文章: http://www.cnblogs.com/artech/p/asp-net-core-di-register.html

     田園里的蟋蟀:https://www.cnblogs.com/xishuai/p/asp-net-core-ioc-di-get-service.html

 

 

2018-10-20,望技術有成后能回來看見自己的腳步

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM