You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Blazor的Scoped服务持续生成新实例?依赖注入问题咨询

Blazor中Scoped服务无法在会话/电路生命周期内保持同一实例的问题

配置与代码示例

Program.cs 服务注册

builder.Services.AddScoped<SessionData>();

SessionData.cs 类实现

public class SessionData
{
    public bool ShowPhoneConfirmationBanner { get; set; }

    public int Id { get; } = _id++;

    private static int _id = 0;
}

MainLayout.razor 注入与使用

[Inject]
private SessionData SessionData { get; set; } = default!;

protected override async Task OnInitializedAsync()
{
     bool show = SessionData.ShowPhoneConfirmationBanner;
     // 业务逻辑处理...
}

问题现象

  • 首次加载Index.razor时,OnInitializedAsync()方法被调用两次,每次获取的SessionData实例都是不同的对象
  • 切换站内任意页面时,MainLayout.OnInitializedAsync()会触发,且每次都会得到新的SessionData实例

测试的导航方式

  • 点击页面原生<a href=...>链接跳转站内页面
  • 在浏览器地址栏直接输入站内页面的完整URL
  • 调用NavigationManager.NavigateTo(url);方法进行跳转
  • 使用DxMenuItem.NavigateUrl(最终渲染为原生<a>标签)

原因分析

Blazor中Scoped服务的生命周期绑定到电路(Circuit)和组件树,出现每次导航都生成新实例的情况,核心原因如下:

  1. 整页刷新触发电路重建:使用原生<a>标签跳转、地址栏输入URL,都会触发浏览器整页刷新,这会完全重建Blazor电路。由于Scoped服务是电路级别的,电路重建后自然会创建新的服务实例。如果调用NavigationManager.NavigateTo时设置了forceLoad: true,同样会触发整页刷新。

  2. 预渲染导致的双实例:Blazor Server默认启用服务器端预渲染(Prerendering),首次加载时会先在服务器生成静态HTML,此时会创建一个临时的Scoped实例;当客户端与服务器建立实时连接后,会重新创建一个用于交互式渲染的实例,这就是首次加载出现两次不同实例的原因。

  3. 组件树随电路重建:整页刷新会导致整个组件树销毁重建,即使Scoped服务是电路级,电路重建后组件重新注入时也会获取新实例。

解决方案

1. 改用客户端导航避免电路重建

  • 替换原生<a>标签为Blazor内置的NavLink组件,它会触发客户端导航,不会导致整页刷新:
    <NavLink href="/target-page">跳转至目标页面</NavLink>
    
  • 使用NavigationManager.NavigateTo时,保持默认的forceLoad: false(客户端导航模式):
    NavigationManager.NavigateTo("/target-page");
    
  • 对于DxMenuItem,若默认的NavigateUrl触发整页刷新,可手动绑定点击事件,调用NavigationManager.NavigateTo实现客户端导航。

2. 消除预渲染带来的实例差异

如果不需要预渲染,可直接禁用它:在_Host.cshtml中修改组件渲染模式,将render-mode="ServerPrerendered"改为render-mode="Server":

<component type="typeof(App)" render-mode="Server" />

若需要保留预渲染,可通过ISession或ProtectedSessionStorage在预渲染和交互式渲染之间同步数据。

3. 谨慎使用Singleton服务

如果需要全局唯一的实例(所有用户共享),可将服务注册为Singleton,但注意这种方式不适合用户会话级数据,且要处理线程安全问题:

builder.Services.AddSingleton<SessionData>();

4. 使用Session存储会话级数据

对于用户专属的会话状态,更可靠的方式是使用ProtectedSessionStorage存储数据,即使电路重建也能从Session中恢复:

// 注入ProtectedSessionStorage
[Inject]
private ProtectedSessionStorage SessionStorage { get; set; } = default!;

// 保存数据
await SessionStorage.SetAsync("ShowPhoneConfirmationBanner", true);

// 获取数据
var result = await SessionStorage.GetAsync<bool>("ShowPhoneConfirmationBanner");
bool show = result.Success ? result.Value : false;

内容的提问来源于stack exchange,提问作者David Thielen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:13:13