依赖注入(Dependency Injection,简称 DI)是一种设计模式,用于将对象的依赖关系从对象内部解耦出来,由外部容器进行管理和提供。在 Blazor 和 ASP.NET Core 中,DI 是内置的核心功能,它通过服务生命周期(Transient、Scoped、Singleton)来管理依赖项。
依赖注入(Dependency Injection)详解
核心概念
-
服务(Service):
- 服务是一个类,封装了某些功能,可以被应用程序的其他部分使用。
-
服务容器(Service Container):
- DI 容器是一个管理服务实例的工具,负责创建和提供服务实例。
- 在 ASP.NET Core 和 Blazor 中,
IServiceCollection
是服务容器的接口,配置服务时通过它进行注册。
-
依赖关系:
- 一个类如果需要另一个类来完成其功能,这种关系称为依赖关系。
DI 的优点
- 解耦: 提高代码的模块化和可维护性。
- 测试性: 通过注入模拟对象(mock)可以轻松测试。
- 生命周期管理: DI 容器负责服务的创建、销毁和共享。
服务的生命周期
在 Blazor 和 ASP.NET Core 中,DI 服务的生命周期有三种:
-
Transient(瞬态):
-
每次请求都会创建一个新的实例。
-
适合轻量级、无状态的服务。
builder.Services.AddTransient<IMyService, MyService>();
-
-
Scoped(作用域):
-
在 Blazor Server 中,每个用户会话(或 SignalR 连接)会创建一个实例,并在整个会话中共享。
-
在 Blazor WebAssembly 中,每个浏览器实例创建一个实例。
-
适合与特定会话相关的服务。
builder.Services.AddScoped<IMyService, MyService>();
-
-
Singleton(单例):
-
应用程序的整个生命周期中只有一个实例。
-
适合全局共享的服务。
builder.Services.AddSingleton<IMyService, MyService>();
-
Scoped 服务详解
Scoped 服务的行为
-
Blazor Server:
- 每个用户连接(SignalR 会话)有一个独立的实例。
- 同一个用户的多个标签页共享同一个实例。
-
Blazor WebAssembly:
- 每个浏览器实例都有独立的
Scoped
服务实例。 - 每个标签页有独立的实例。
- 每个浏览器实例都有独立的
实现 Scoped 服务
创建 Scoped 服务
-
创建一个服务类:
public class UserInfoService { public string UserName { get; set; } public string Role { get; set; } }
-
在
Program.cs
中注册服务:builder.Services.AddScoped<UserInfoService>();
使用 Scoped 服务
在页面或组件中注入服务:
@inject UserInfoService UserInfoService
<p>当前用户: @UserInfoService.UserName</p>
在组件的代码部分更新或读取服务状态:
<button @onclick="SetUserInfo">设置用户信息</button>
@code
{
private void SetUserInfo()
{
UserInfoService.UserName = "John Doe";
UserInfoService.Role = "Admin";
}
}
Scoped 服务的作用与场景
-
用户会话管理:
- 保存用户的基本信息(用户名、角色等)。
- 保存会话中需要共享的临时数据。
-
中间状态存储:
- 用于在多步操作中保存状态(如分步骤表单)。
-
与数据库或外部服务交互:
- 每个用户操作的独立上下文(如数据库上下文
DbContext
)。
- 每个用户操作的独立上下文(如数据库上下文
Scoped 服务的注意事项
-
线程安全:
- 在 Blazor Server 中,多个组件可能同时访问
Scoped
服务,确保使用线程安全的方式处理数据。
- 在 Blazor Server 中,多个组件可能同时访问
-
避免与 Singleton 服务共享数据:
- 如果需要共享 Scoped 服务与 Singleton 服务的数据,需特别小心数据一致性问题。
-
Blazor Server 中多标签页问题:
- 同一个用户的多个标签页共享 Scoped 服务实例。如果需要为每个标签页提供独立的状态,可以结合
Circuit
或切换为Transient
服务。
- 同一个用户的多个标签页共享 Scoped 服务实例。如果需要为每个标签页提供独立的状态,可以结合
Scoped 服务与其他生命周期的对比
生命周期 | 实例数 | 适用场景 |
---|---|---|
Transient | 每次请求创建新实例 | 无状态或轻量级的短期操作服务 |
Scoped | 每个用户会话或浏览器实例一个 | 用户会话、临时状态 |
Singleton | 整个应用程序一个实例 | 全局共享服务(如配置或缓存服务) |
总结
- Scoped 服务非常适合在 Blazor 中管理与用户会话相关的数据和状态。
- 在 Blazor Server 中需要注意多标签页共享的问题;在 Blazor WebAssembly 中无需担心。
- 使用 Scoped 服务时,保持线程安全是关键,尤其在 Blazor Server 环境下。
根据实际需求选择合适的服务生命周期,以实现高效、可靠的应用设计。