Blazor Server应用Scoped DI对象运行时更新后值丢失问题排查
Blazor Server Scoped服务生命周期的误解
传统ASP.NET Core的Scoped服务是绑定HTTP请求生命周期,但Blazor Server的Scoped服务是绑定到**SignalR电路(Circuit)**的。核心问题在于:用户未认证时会建立匿名电路,若登录后触发页面强制刷新(比如NavigateTo第二个参数设为true),浏览器会发起新HTTP请求并创建认证电路,原Scoped服务实例会被回收,新实例的CurrentRequestContext是初始状态,导致用户信息丢失。服务注册方式的问题
你在注册UIDIWrapper时手动new CurrentRequestContext(),未将CurrentRequestContext本身注册为Scoped服务。这种方式不仅不符合依赖注入设计规范,更关键的是电路重建时,新的UIDIWrapper会重新创建空的CurrentRequestContext,之前设置的用户信息自然丢失。登录后导航方式的影响
若登录后使用强制刷新导航,会断开当前SignalR电路并重建,原有Scoped服务实例被销毁,新实例的状态为空,直接导致用户信息丢失。
方案1:利用Blazor内置认证状态同步(推荐)
Blazor Server原生集成认证系统,可直接通过AuthenticationStateProvider获取当前用户,无需手动维护状态:
// 组件中注入AuthenticationStateProvider [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; // 获取当前认证用户 private async Task LoadCurrentUser() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity?.IsAuthenticated == true) { // 从Claim解析用户信息(示例) var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value; var userName = user.FindFirst(ClaimTypes.Name)?.Value; // 转换为你的aUser对象 } }
这种方式会自动跟随认证状态变化,不会出现信息丢失问题,AuthenticationStateProvider会在认证状态变更时通知所有组件。
方案2:修正自定义状态的使用逻辑
若坚持使用自定义CurrentRequestContext,可按以下步骤调整:
- 将CurrentRequestContext注册为Scoped服务
public static void AppScopeObjects(this IServiceCollection services) { services.AddScoped<CurrentRequestContext>(); services.AddScoped<UIDIWrapper>(x => new UIDIWrapper() { PreventDataOverwriteService = x.GetRequiredService<IPreventDataOverwriteService>(), CurrentContext = x.GetRequiredService<CurrentRequestContext>() }); }
- 登录后避免强制刷新导航
登录完成后使用无强制刷新的导航,保留当前SignalR电路:
// 不要设置第二个参数为true NavigationManager.NavigateTo("/dashboard");
- 处理页面刷新场景
用户手动刷新页面会重建电路,需从认证系统重新加载用户信息到CurrentRequestContext,可在全局布局或初始化逻辑中添加:
[Inject] private UIDIWrapper Wrapper { get; set; } = default!; [Inject] private AuthenticationStateProvider AuthStateProvider { get; set; } = default!; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity?.IsAuthenticated == true) { // 从Claim解析用户信息并赋值 Wrapper.CurrentContext.LoggedInUser = ParseUserFromClaims(user.Claims); } } // 自定义解析方法 private YourUserType ParseUserFromClaims(IEnumerable<Claim> claims) { // 实现从Claim到用户对象的转换逻辑 return new YourUserType { Id = claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value, Name = claims.First(c => c.Type == ClaimTypes.Name).Value }; }
- Blazor Server的Scoped服务生命周期绑定SignalR电路,而非传统HTTP请求;
- 登录后强制刷新会重建电路,导致原有Scoped实例丢失;
- 优先使用Blazor内置
AuthenticationStateProvider管理用户状态,避免手动维护的风险; - 若自定义状态,需在电路重建时(如页面刷新)从认证系统恢复状态。
内容的提问来源于stack exchange,提问作者Ramesh

