ASP.NET Core 提供了可以在代码中进行依赖注入的服务生命周期选项。
遵循SOLID原则中的“D”原则,即“高层模块不应依赖于低层模块,它们应该依赖于抽象”。ASP.NET Core 通过构造函数大量使用依赖注入,并且提供了服务的生命周期 — 范围、瞬时和单例。
服务生命周期的目标是在我们的应用程序中注入不同的服务,并确定这些服务是如何被创建和复用的,以及它们的复用方式。
在 .NET Core 中有四种服务生命周期类型。
单例服务的生命周期这种单例生命周期确保对象只会被创建一次,并在管道中反复使用。所以无论你怎样或多少次调用这个对象,一旦实例化,它的值将一直保持不变,直到主机重启。单例生命周期最适合用于无状态的对象,比如“日志”记录。
这些就像是这样的全局变量。
让我们以一个叫做 ClientId 的东西为例,它只是返回一个 Guid 值。
public class 客户端ID
{
public Guid Id;
public 客户端ID()
{
Id = Guid.NewGuid();
}
public Guid 显示GUID()
{
return Id;
}
}
这个类在构造函数中分配并初始化一个 Guid,并定义了一个输出 Guid 值的方法。在服务集合中,此类被注入为单例服务。
services.AddSingleton<ClientId>(); // 这行代码为服务添加了一个单例ClientId对象
using Microsoft.AspNetCore.Mvc; // 使用 Microsoft.AspNetCore.Mvc;
namespace Lifetime.Controller; // 命名空间 Lifetime.Controller;
[ApiController] // API控制器特性
[Route("api/")] // 路由
public class LifetimeController : ControllerBase // 公共类 LifetimeController 继承自 ControllerBase
{
ClientId clientId; // 客户端ID
public LifetimeController(ClientId clientId) // 构造函数
{
this.clientId = clientId; // 初始化客户端ID
}
[HttpGet("lifetime")] // GET请求
public IActionResult Get() // 获取方法
{
return Ok(clientId.ShowGuid()); // 返回客户端ID的GUID
}
}
无论 Singleton() 端点被调用多少次,它都会返回同一个 Guid,直到主机重启。
作用域服务生命周期作用域服务的生命周期每个请求只创建一次,并在请求处理管道中使用
如果实例在构造函数中多次初始化,整个请求过程中都会使用同一个实例。
使用之前的代码,如果我们把 ServiceCollection
中的服务生命周期改为 Scoped 服务,你就会看到 Guid 的值每次都不同。而在控制器中,如果我们每次在构造函数中解析 ClientId
两次并运行代码,你就会看到每次请求时 ClientId
使用的是同一个实例,也就是说。
services.AddScoped<ClientId>();
以下是代码片段的解释:
这段代码将 ClientId
注册为一个单例服务。
using Microsoft.AspNetCore.Mvc; // 使用Microsoft ASP.NET Core MVC库
namespace Lifetime.Controller; // 命名空间Lifetime.Controller
[ApiController] // 标记为API控制器
[Route("api/")] // 路由到/api/
public class LifetimeController : ControllerBase // 定义LifetimeController类继承自ControllerBase
{
ClientId clientId; // 客户端ID1
ClientId clientId2; // 客户端ID2
public LifetimeController(ClientId clientId, ClientId clientId2) // 构造函数
{
this.clientId = clientId; // 初始化clientId
this.clientId2 = clientId2; // 初始化clientId2
}
[HttpGet("lifetime")] // 标记为GET请求,路由到/lifetime
public IActionResult Get() // 定义Get方法
{
// 返回一个包含两个客户端ID的OK响应
return Ok(new{id1 = clientId.ShowGuid(), id2=clientId2.ShowGuid()});
}
}
在实体框架中最适合使用范围服务的地方是,在那里我们可以只实例化一次DbContext和泛型仓储对象,并在整个请求过程中复用它们。我们不想在每次请求时都创建新的实例。
service.AddDbContext<BelemaContext>(options =>
{
options.UseNpgsql(configuration["ConnectionString"], opt =>
{
opt.EnableRetryOnFailure(15, TimeSpan.FromSeconds(30), errorCodesToAdd: null); // 启用重试失败功能,最多重试15次,每次重试间隔30秒
});
});
暂时服务生命周期默认作用域
每次请求都会创建一个新的瞬时服务。如果我们运行上述代码,并将服务生命周期更改为瞬时,你会发现这些值每次都不同,因为它们每次请求都会被创建。它们最适合用于轻量级无状态服务。
services.AddTransient<ClientId>();
using Microsoft.AspNetCore.Mvc;
namespace Lifetime.Controllers;
[ApiController]
[Route("api/")]
public class LifetimeController : ControllerBase
{
ClientId clientId;
ClientId clientId2;
/// <summary>
/// 构造函数,初始化两个客户端ID
/// </summary>
/// <param name="clientId">第一个客户端ID</param>
/// <param name="clientId2">第二个客户端ID</param>
public LifetimeController(ClientId clientId, ClientId clientId2)
{
this.clientId = clientId;
this.clientId2 = clientId2;
}
[HttpGet("lifetime")]
public IActionResult Get()
{
// 返回包含两个客户端ID的响应
return Ok(new { id1 = clientId.ShowGuid(), id2 = clientId2.ShowGuid() });
}
}
键控服务
ASP.NET Core 8 引入了一种新的服务生命周期,称为 KeyedServices。KeyedServices 通过分配键来管理不同服务生命周期的机制。有三种键服务,分别是:
- 添加带有键的scoped服务
- 添加带有键的单例服务
- 添加带有键的暂时服务
它是这样运作的:
services.AddKeyedSingleton<ClientId>("key1"); //key1 是一个键,表示特定的服务实例
using Microsoft.AspNetCore.Mvc;
命名空间 Lifetime.Controllers;
[ApiController]
[Route("api/")]
public class LifetimeController : ControllerBase
{
[HttpGet("lifetime")]
public IActionResult Get([FromKeyedServices("key1")] ClientId client)
{
return Ok(client.ShowGuid());
}
}
为进一步提升您的ASP.NET Core技能,您可以注册此课程Udemy,该课程从基础知识开始,并提供源代码。
也可以看看我关于如何从头开始构建领域驱动设计(DDD)项目的这篇介绍文章。
如果你想学习一个强大的前端框架,可以看看涵盖了TypeScript、React和Next.js的入门课程。该课程还包括一个用.NET开发的后端,进一步提高你的软件开发技能。
编程愉快!