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

Blazor Server重写DbContext SaveChangesAsync获取用户名遇预渲染错误

问题原因与解决方案

问题原因

报错的核心是Blazor Server默认启用的预渲染(Prerendering)机制:

  • 预渲染阶段,服务器端尚未与浏览器建立SignalR连接,无法执行JavaScript互操作(你使用的Blazored.SessionStorage依赖浏览器端存储,必须通过JS调用才能访问)。
  • 你的CustomAuthStateProvider在GetAuthenticationStateAsync中直接调用ISessionStorageService,当DbContext在预渲染阶段触发SaveChangesAsync时,会间接触发这个JS互操作,导致抛出异常。
  • 虽然DbContext属于DAL项目,但调用它的Blazor组件处于预渲染生命周期中,整个请求上下文受预渲染限制。

解决方案

方案1:架构解耦(推荐)

DbContext作为数据访问层,不应依赖Blazor的AuthenticationStateProvider,直接从调用方传递审计所需的用户信息:

  1. 修改DbContext的SaveChangesAsync方法,接收用户名参数:
public async Task<int> SaveChangesAsync(string userName, CancellationToken cancellationToken = default)
{
    // 在SetAudit中使用传入的userName设置审计字段
    SetAudit(userName);
    return await base.SaveChangesAsync(cancellationToken);
}
  1. 在Blazor组件/业务层中,先获取认证状态,再调用修改后的Save方法:
@inject AuthenticationStateProvider AuthStateProvider
@inject YourDbContext DbContext

private async Task SubmitData()
{
    var authState = await AuthStateProvider.GetAuthenticationStateAsync();
    var userName = authState.User.Identity.Name ?? "Anonymous";
    await DbContext.SaveChangesAsync(userName);
}

方案2:让认证提供者兼容预渲染

如果必须在DbContext中获取用户信息,可以修改CustomAuthStateProvider,在预渲染阶段返回匿名身份,避免触发JS互操作:

  1. 注入IHttpContextAccessor用于判断预渲染状态:
public class CustomAuthStateProvider : AuthenticationStateProvider
{
    private readonly ISessionStorageService _sessionStorageService;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CustomAuthStateProvider(ISessionStorageService sessionStorageService, 
                                   IHttpContextAccessor httpContextAccessor)
    {
        _sessionStorageService = sessionStorageService;
        _httpContextAccessor = httpContextAccessor;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        // 检查是否处于预渲染阶段
        var isPrerendering = _httpContextAccessor.HttpContext?.Items.ContainsKey("__Prerendering") == true;
        if (isPrerendering)
        {
            // 预渲染时返回匿名身份,避免JS互操作
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        string authToken = await _sessionStorageService.GetItemAsStringAsync("authToken");
        var identity = new ClaimsIdentity();

        if (!string.IsNullOrEmpty(authToken))
        {
            try
            {
                identity = new ClaimsIdentity(ParseClaimsFromJwt(authToken), "jwt");
            }
            catch
            {
                await _sessionStorageService.RemoveItemAsync("authToken");
                identity = new ClaimsIdentity();
            }
        }

        var user = new ClaimsPrincipal(identity);
        var state = new AuthenticationState(user);

        NotifyAuthenticationStateChanged(Task.FromResult(state));
        return state;
    }

    // 保留原有的ParseBase64WithoutPadding和ParseClaimsFromJwt方法
}
  1. 在Program.cs中注册IHttpContextAccessor:
builder.Services.AddHttpContextAccessor();

方案3:关闭预渲染(临时应急)

修改_Host.cshtml中的渲染模式,从ServerPrerendered改为Server,彻底禁用预渲染:

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

注意:此方案会降低首屏加载速度,仅作为临时解决方案,不推荐长期使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:57:31