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

目录

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

Demo地址: 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>();

代码设计服务生命周期

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

public interface IOperation
{
    Guid GetGuid();
}


public interface IOperationTransient : IOperation
{
}


public interface IOperationScoped : IOperation
{
}


public interface IOperationSingleton : IOperation
{      
}


public interface IOperationInstance : IOperation
{
}

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

/// <summary>
/// 常规服务
/// </summary>
public class Operation : IOperation
{
  private readonly Guid _guid;


  public Operation()
  {
       _guid = Guid.NewGuid();
   }


   public Operation(Guid guid)
   {
       _guid = guid == Guid.Empty? Guid.NewGuid() : guid;
   }


   public Guid GetGuid()
   {
       return _guid;
   }
}


/// <summary>
/// 瞬时服务
/// </summary>
public class OperationTransient : IOperationTransient
{
   private readonly Guid _guid;


   public OperationTransient()
   {
       _guid = Guid.NewGuid();
   }


   public OperationTransient(Guid guid)
   {
       _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
   }


   public Guid GetGuid()
   {
       return _guid;
   }
}


/// <summary>
/// 单次请求内服务固定
/// </summary>
public class OperationScoped : IOperationScoped
{
   private readonly Guid _guid;


   public OperationScoped()
   {
       _guid = Guid.NewGuid();
   }


   public OperationScoped(Guid guid)
   {
       _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
   }


   public Guid GetGuid()
   {
       return _guid;
   }
}


/// <summary>
/// 所有请求内固定服务
/// </summary>
public class OperationSingleton : IOperationSingleton
{
   private readonly Guid _guid;


   public OperationSingleton()
   {
       _guid = Guid.NewGuid();
   }


   public OperationSingleton(Guid guid)
   {
       _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
   }


   public Guid GetGuid()
   {
       return _guid;
   }
}


/// <summary>
/// 应用程序内固定服务
/// </summary>
public class OperationInstance : IOperationInstance
{
   private readonly Guid _guid;


    public OperationInstance()
    {
        _guid = Guid.NewGuid();
    }


    public OperationInstance(Guid guid)
    {
        _guid = guid == Guid.Empty ? Guid.NewGuid() : guid;
    }


    public Guid GetGuid()
    {
        return _guid;
    }
}

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

public interface IOperationService
{
    /// <summary>
    /// 获取四种形式的Guid码
    /// </summary>
    /// <returns></returns>
    List<string> GetGuidString();
}

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

/// <summary>
/// 服务调用
/// </summary>
public class OperationService : IOperationService
{
  public IOperationTransient _transientOperation { get; }
  public IOperationScoped _scopedOperation { get; }
  public IOperationSingleton _singletonOperation { get; }
  public IOperationInstance _instanceOperation { get; }


   public OperationService(IOperationTransient transientOperation,
       IOperationScoped scopedOperation,
       IOperationSingleton singletonOperation,
       IOperationInstance instanceOperation)
   {
       _transientOperation = transientOperation;
       _scopedOperation = scopedOperation;
       _singletonOperation = singletonOperation;
       _instanceOperation = instanceOperation;
   }


   public List<string> GetGuidString()
   {
       return new List<string>()
       {
           $"Transient:"+_transientOperation.GetGuid(),
           $"Scoped:"+_scopedOperation.GetGuid(),
           $"Singleton:" +_singletonOperation.GetGuid(),
           $"Instance:"+_instanceOperation.GetGuid(),
       };
   }
}

在控制器中进行服务注入

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IOperationService _operationService;


    public ValuesController(IOperationService operationService)
    {
        _operationService = operationService;
    }


    [HttpGet]
    [Route(nameof(GetGuidString))]
    public ActionResult<string> GetGuidString()
    {
        return string.Join("\n", _operationService.GetGuidString());
    }
}

在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信息, 第一次启动程序(不关闭)发起访问:

200426128_0e270303-e8a0-4259-b733-819a411b6af0 第二次(第一次基础上再次访问)发起访问:

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

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

200428513_1c0e751c-f0d3-4055-a6ae-307711d5a4b5 可以见到,Singleton和Instance都发生了变化,也说明了之前在Singleton和Instance处写上的作用。

接下来开始设计Transient和Scoped的不同之处,对于已有代码加上新功能,此次我们只针对Scoped和Transient进行比较。

首先在StartUp中将HttpContextAccessor服务注入,目的是在后期能够针对Scoped获取新的服务实例(尽管两个实例是相同的)。

services.AddHttpContextAccessor();

接着在聚合服务中增加一个方法,用来针对Transient、Scoped测试。

/// <summary>
/// 获取Transient、Scoped的Guid码
/// </summary>
/// <returns></returns>
List<string> GetTransientAndScopedGuidString();

在聚合服务实现中实现该方法并对已有的服务重新获取实例,得到不同实例下的Guid码。

public List<string> GetTransientAndScopedGuidString()
{
    //var tempTransientService = (IOperationTransient)ServiceLocator.Instance.GetService(typeof(IOperationTransient));


    var tempTransientService = (IOperationTransient)_httpContextAccessor.HttpContext.RequestServices.GetService(typeof(IOperationTransient));
    var tempScopedService = (IOperationScoped)_httpContextAccessor.HttpContext.RequestServices.GetService(typeof(IOperationScoped));


    return new List<string>()
    {
        $"原生Transient请求服务:"+_transientOperation.GetGuid(),
        $"手动Transient请求服务:"+ tempTransientService.GetGuid(),
        $"原生Scoped请求服务:"+_scopedOperation.GetGuid(),
        $"手动Scoped请求服务:"+tempScopedService.GetGuid(),
    };
}

在控制器部分调用该聚合服务即可,并返回相应的结果,本次我返回的结果: 200429527_8570ba19-a832-4d7f-b1fe-9db44433a7f1 可以看到,对于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,望技术有成后能回来看见自己的脚步