.net core 服务生命周期
服务生存期
为每个注册的服务选择适当的生存期。 可以使用以下生存期配置 ASP.NET Core 服务:
暂时
暂时生存期服务 (AddTransient) 是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。
范围内
作用域生存期服务 (AddScoped) 以每个客户端请求(连接)一次的方式创建。
警告
在中间件内使用有作用域的服务时,请将该服务注入至 Invoke
或 InvokeAsync
方法。 请不要通过构造函数注入进行注入,因为它会强制服务的行为与单一实例类似。 有关详细信息,请参阅 写入自定义 ASP.NET Core 中间件。
单例
单一实例生存期服务 (AddSingleton) 是在第一次请求时(或者在运行 Startup.ConfigureServices
并且使用服务注册指定实例时)创建的。 每个后续请求都使用相同的实例。 如果应用需要单一实例行为,建议允许服务容器管理服务的生存期。 不要实现单一实例设计模式并提供用户代码来管理对象在类中的生存期。
警告
从单一实例解析有作用域的服务很危险。 当处理后续请求时,它可能会导致服务处于不正确的状态。
服务注册方法
服务注册扩展方法提供适用于特定场景的重载。
方法 | 自动 对象 (object) 处置 | 多种 实现 | 传递参数 |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() 示例: services.AddSingleton<IMyDep, MyDep>(); |
是 | 是 | No |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) 示例: services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep("A string!")); |
是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>() 示例: services.AddSingleton<MyDep>(); |
是 | 否 | No |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) 示例: services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep("A string!")); |
No | 是 | 是 |
AddSingleton(new {IMPLEMENTATION}) 示例: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep("A string!")); |
No | 否 | 是 |
要详细了解类型处置,请参阅服务处置部分。 多个实现的常见场景是为测试模拟类型。
TryAdd{LIFETIME}
方法仅当尚未注册实现时,注册该服务。
在以下示例中,第一行向 IMyDependency
注册 MyDependency
。 第二行没有任何作用,因为 IMyDependency
已有一个已注册的实现:
services.AddSingleton<IMyDependency, MyDependency>();
// The following line has no effect:
services.TryAddSingleton<IMyDependency, DifferentDependency>();
有关详细信息,请参见:
TryAddEnumerable(ServiceDescriptor) 方法仅当没有同一类型的实现时,注册该服务。 多个服务通过 IEnumerable<{SERVICE}>
解析。 注册服务时,开发人员只希望在尚未添加一个相同类型时添加实例。 通常情况下,库创建者使用此方法来避免在容器中注册一个实例的两个副本。
在以下示例中,第一行向 IMyDep1
注册 MyDep
。 第二行向 IMyDep2
注册 MyDep
。 第三行没有任何作用,因为 IMyDep1
已有一个 MyDep
的已注册的实现:
public interface IMyDep1 {}
public interface IMyDep2 {}
public class MyDep : IMyDep1, IMyDep2 {}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep2, MyDep>());
// Two registrations of MyDep for IMyDep1 is avoided by the following line:
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>());
构造函数注入行为
服务可以通过两种机制来解析:
- IServiceProvider
- ActivatorUtilities – 允许在依赖关系注入容器中创建没有服务注册的对象。
ActivatorUtilities
用于面向用户的抽象,例如标记帮助器、MVC 控制器和模型绑定器。
构造函数可以接受依赖关系注入不提供的参数,但参数必须分配默认值。
当服务由 IServiceProvider
或 ActivatorUtilities
解析时,构造函数注入需要 public 构造函数。
当服务由 ActivatorUtilities
解析时,构造函数注入要求只存在一个适用的构造函数。 支持构造函数重载,但其参数可以全部通过依赖注入来实现的重载只能存在一个。
实体框架上下文
通常使用设置了范围的生存期将实体框架上下文添加到服务容器中,因为 Web 应用数据库操作通常将范围设置为客户端请求。 如果在注册数据库上下文时,AddDbContext<TContext> 重载未指定生存期,则设置默认生存期范围。 给定生存期的服务不应使用生存期比服务短的数据库上下文。
生存期和注册选项
为了演示生存期和注册选项之间的差异,请考虑以下接口,将任务表示为具有唯一标识符 OperationId
的操作。 根据为以下接口配置操作服务的生存期的方式,容器在类请求时提供相同或不同的服务实例:
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
接口在 Operation
类中实现。 Operation
构造函数将生成一个 GUID(如果未提供):
public class Operation : IOperationTransient,
IOperationScoped,
IOperationSingleton,
IOperationSingletonInstance
{
public Operation() : this(Guid.NewGuid())
{
}
public Operation(Guid id)
{
OperationId = id;
}
public Guid OperationId { get; private set; }
}
注册 OperationService
取决于,每个其他 Operation
类型。 当通过依赖关系注入请求 OperationService
时,它将接收每个服务的新实例或基于从属服务的生存期的现有实例。
- 如果从容器请求时创建了临时服务,则
IOperationTransient
服务的OperationId
与OperationService
的OperationId
不同。OperationService
将接收IOperationTransient
类的新实例。 新实例将生成一个不同的OperationId
。 - 如果按客户端请求创建有作用域的服务,则
IOperationScoped
服务的OperationId
与客户端请求中OperationService
的该 ID 相同。 在客户端请求中,两个服务共享不同的OperationId
值。 - 如果单一数据库和单一实例服务只创建一次并在所有客户端请求和所有服务中使用,则
OperationId
在所有服务请求中保持不变。
public class OperationService
{
public OperationService(
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
}
在 Startup.ConfigureServices
中,根据其指定的生存期,将每个类型添加到容器中:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
// OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
IOperationSingletonInstance
服务正在使用已知 ID 为 Guid.Empty
的特定实例。 此类型在使用时很明显(其 GUID 全部为零)。
示例应用演示了各个请求中和之间的对象生存期。 示例应用的 IndexModel
请求每种 IOperation
类型和 OperationService
。 然后,页面通过属性分配显示所有页面模型类和服务的 OperationId
值:
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
以下两个输出显示了两个请求的结果:
第一个请求:
控制器操作:
暂时性:d233e165-f417-469b-a866-1cf1935d2518
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
OperationService
操作:
暂时性:c6b049eb-1318-4e31-90f1-eb2dd849ff64
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
第二个请求:
控制器操作:
暂时性:b63bd538-0a37-4ff1-90ba-081c5138dda0
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
OperationService
操作:
暂时性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
观察哪个 OperationId
值会在一个请求之内和不同请求之间变化:
- 暂时性 对象始终不同。 第一个和第二个客户端请求的暂时性
OperationId
值对于OperationService
操作和在客户端请求内都是不同的。 为每个服务请求和客户端请求提供了一个新实例。 - 作用域 对象在一个客户端请求中是相同的,但在多个客户端请求中是不同的。
- 单一实例 对象对每个对象和每个请求都是相同的(不管
Startup.ConfigureServices
中是否提供Operation
实例)。