.NET 8 Blazor中如何加载与存储用户信息?
在Blazor .NET 8中实现类似ASP.NET MVC Session_Start的用户信息加载逻辑
问题背景
此前在ASP.NET MVC中,通过处理Session_Start事件加载用户信息并存入会话变量,避免每次页面加载重复查询数据库。现在想在.NET 8服务器端Blazor(使用Windows身份验证)中实现相同逻辑,选择在App.razor的OnInitializedAsync方法中编写代码,但遇到了JavaScript互操作相关的错误。
现有代码
@code { protected override async Task OnInitializedAsync() { UserDetails details; var user = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User; var userId = user.Identity?.Name?.ToLower(); var result = await BrowserStorage.GetAsync<UserDetails>(userId); if (result.Success) { details = (UserDetails)result.Value; } else { GetUserDetailsQuery query = new GetUserDetailsQuery(); query.UserLogin = await getUserId(); details = query.Handle(); await BrowserStorage.SetAsync(userId, details); } } }
遇到的错误
InvalidOperationException: JavaScript interop calls cannot be issued at this time.
解决办法及最佳实践
1. 错误原因
服务器端Blazor中,OnInitializedAsync的执行时机过早,此时JavaScript运行时还未完成初始化,无法调用BrowserStorage这类依赖JS互操作的API。
2. 可行替代方案
方案一:改用OnAfterRenderAsync并控制首次执行
将逻辑移至OnAfterRenderAsync方法,仅在首次渲染时执行,确保JS运行时已准备就绪:
private bool _isInitialized = false; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && !_isInitialized) { _isInitialized = true; UserDetails details; var user = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User; var userId = user.Identity?.Name?.ToLower(); var result = await BrowserStorage.GetAsync<UserDetails>(userId); if (result.Success) { details = (UserDetails)result.Value; } else { GetUserDetailsQuery query = new GetUserDetailsQuery(); query.UserLogin = await getUserId(); details = query.Handle(); await BrowserStorage.SetAsync(userId, details); } } }
方案二:使用服务器端会话存储(更贴近MVC原有逻辑)
既然是服务器端Blazor,可直接使用ASP.NET Core的ISession存储用户信息,完全规避JS互操作问题:
- 在
Program.cs中启用会话:
builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromHours(1); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 注意中间件顺序,添加在UseRouting之后 app.UseSession();
- 在
App.razor中注入IHttpContextAccessor访问会话:
@inject IHttpContextAccessor HttpContextAccessor @code { protected override async Task OnInitializedAsync() { var user = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User; var userId = user.Identity?.Name?.ToLower(); var session = HttpContextAccessor.HttpContext?.Session; if (session != null && userId != null) { UserDetails details; var storedDetails = session.GetString(userId); if (!string.IsNullOrEmpty(storedDetails)) { details = JsonSerializer.Deserialize<UserDetails>(storedDetails); } else { GetUserDetailsQuery query = new GetUserDetailsQuery(); query.UserLogin = await getUserId(); details = query.Handle(); session.SetString(userId, JsonSerializer.Serialize(details)); } } } }
方案三:使用全局状态容器
创建全局用户状态服务,在用户首次认证完成后加载一次信息,后续组件直接从状态容器获取:
- 定义状态服务:
public class UserStateService { public UserDetails CurrentUser { get; set; } public bool IsLoaded { get; set; } }
- 在
Program.cs中注册为单例:
builder.Services.AddSingleton<UserStateService>();
- 在
App.razor中注入并初始化:
@inject UserStateService UserState @inject AuthenticationStateProvider AuthStateProvider @code { protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; var userId = user.Identity?.Name?.ToLower(); if (!UserState.IsLoaded && userId != null) { GetUserDetailsQuery query = new GetUserDetailsQuery(); query.UserLogin = await getUserId(); UserState.CurrentUser = query.Handle(); UserState.IsLoaded = true; } } }
后续任意组件只需注入UserStateService即可直接获取用户信息,无需重复查询。
3. 是否需要每次页面加载查询数据库?
不需要。只要用户会话有效,无论是使用浏览器存储、服务器会话还是全局状态容器,都可以仅在首次加载时查询一次数据库,后续直接从存储/状态中获取,和你之前MVC的逻辑保持一致。
内容的提问来源于stack exchange,提问作者MojoDK
相关产品推荐
相关产品推荐

