Asp.Net Core中服务的生命周期选项区别和用法

时间:2022-12-24 16:42:32

  在做一个小的Demo中,在一个界面上两次调用视图组件,并且在视图组件中都调用了数据库查询,结果发现,一直报错,将两个视图组件的调用分离,单独进行,却又是正常的,寻找一番,发现是配置依赖注入服务时,对于服务的生命周期没有配置得当导致,特此做一次实验来认识三者之间(甚至是四者之间的用法及区别)。

  本文demo地址(具体见WebApi控制器中):https://gitee.com/530521314/koInstance.git

 

一、服务的生命周期

  在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>();

 

二、代码设计服务生命周期

  首先设计一些服务相关的操作接口

Asp.Net Core中服务的生命周期选项区别和用法Asp.Net Core中服务的生命周期选项区别和用法
 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     }
基础服务接口

  其次对这些操作类予以实现并生成相关服务

Asp.Net Core中服务的生命周期选项区别和用法Asp.Net Core中服务的生命周期选项区别和用法
  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     }
基础服务具体实现

  对基础服务的聚合接口,提供统一服务接口

Asp.Net Core中服务的生命周期选项区别和用法Asp.Net Core中服务的生命周期选项区别和用法
    public interface IOperationService
    {
        /// <summary>
        /// 获取四种形式的Guid码
        /// </summary>
        /// <returns></returns>
        List<string> GetGuidString();
    }
聚合服务接口

  对基础服务的聚合实现,将基础服务全部接入进来作为统一服务

Asp.Net Core中服务的生命周期选项区别和用法Asp.Net Core中服务的生命周期选项区别和用法
 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信息,

  第一次启动程序(不关闭)发起访问:

  Asp.Net Core中服务的生命周期选项区别和用法

  第二次(第一次基础上再次访问)发起访问:

  Asp.Net Core中服务的生命周期选项区别和用法

  可以看见,两次访问下,Singleton和Instance是相同的,都是由应用程序启动时和应用服务加载时决定完毕,Singleton在首次进入服务时进行分配,并始终保持不变,而Instance在应用程序启动时,便将实例注入,进入服务也保持着最先的实例,没有重新分配实例。而Transient和Scoped则进行着变化。

  关闭程序,重启,第三次发起访问:

  Asp.Net Core中服务的生命周期选项区别和用法

  可以见到,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     }

  在控制器部分调用该聚合服务即可,并返回相应的结果,本次我返回的结果:

  Asp.Net Core中服务的生命周期选项区别和用法

  可以看到,对于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,望技术有成后能回来看见自己的脚步