在做一個小的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,望技術有成后能回來看見自己的腳步
