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

Blazor Server:无法从HttpMessageHandler访问ProtectedSessionStorage值

问题解决:Blazor Server中DelegatingHandler访问ProtectedSessionStorage时的JS互操作异常

问题概述

开发调用外部API的Blazor Server应用时,登录后将JWT令牌存入ProtectedSessionStorage,Razor组件中能正常获取令牌,但自定义DelegatingHandler中调用获取令牌时抛出异常:

System.InvalidOperationException: '此时无法发出JavaScript互操作调用。这是因为组件正在进行静态渲染。启用预渲染时,只能在OnAfterRenderAsync生命周期方法中执行JavaScript互操作调用。'

原因分析

Blazor Server默认启用预渲染,在预渲染阶段(组件初始化早期),服务器尚未与客户端建立JS连接,无法执行JS互操作。ProtectedSessionStorage依赖浏览器sessionStorage,必须通过JS互操作访问;若DelegatingHandler在预渲染阶段被触发(比如组件在OnInitializedAsync中调用HttpClient),就会触发该异常。

解决方案

方案1:延迟HttpClient调用至OnAfterRenderAsync阶段

组件中避免在OnInitializedAsync等预渲染阶段调用HttpClient,移至OnAfterRenderAsync(该阶段JS环境已就绪):

@inject IHttpClientFactory HttpClientFactory
@inject TokenService tokenService

<p>@message</p>

@code {
    string message = "";
    bool _hasRendered = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && !_hasRendered)
        {
            _hasRendered = true;
            // 此时JS互操作可用,可安全调用HttpClient
            var httpClient = HttpClientFactory.CreateClient("AuthenticatedClient");
            var response = await httpClient.GetAsync("/api/data");

            if (response.IsSuccessStatusCode)
            {
                message = "授权成功";
                var result = await response.Content.ReadAsStringAsync();
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
            {
                message = "未授权";
            }
            StateHasChanged();
        }
        await base.OnAfterRenderAsync(firstRender);
    }
}

方案2:将令牌存储在服务器端(推荐)

避免依赖客户端存储,登录后将令牌存入服务器端HttpContext.Session或用户ClaimsPrincipal中,DelegatingHandler无需JS互操作即可获取令牌:

修改TokenService,从ClaimsPrincipal获取令牌

public class TokenService(IHttpContextAccessor httpContextAccessor)
{
    public string GetToken()
    {
        var httpContext = httpContextAccessor.HttpContext;
        if (httpContext == null) return string.Empty;
        // 假设登录时已将令牌存入Claims
        var tokenClaim = httpContext.User.Claims.FirstOrDefault(c => c.Type == "AccessToken");
        return tokenClaim?.Value ?? string.Empty;
    }
}

更新AuthTokenHandler

public class AuthTokenHandler(TokenService tokenService) : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = tokenService.GetToken();// 无需异步,避免JS互操作
        if (!string.IsNullOrEmpty(token))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

服务注册更新

// 添加HttpContextAccessor
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<TokenService>();
builder.Services.AddScoped<AuthTokenHandler>();
builder.Services.AddHttpClient("AuthenticatedClient", client =>
{
    client.BaseAddress = new Uri(BaseUrl);
}).AddHttpMessageHandler<AuthTokenHandler>();

方案3:禁用预渲染

修改_Host.cshtml,移除预渲染标记:

<!-- 原代码 -->
<component type="typeof(App)" render-mode="ServerPrerendered" />

<!-- 修改后 -->
<component type="typeof(App)" render-mode="Server" />

此方法简单,但会导致首屏加载变慢,需等待Blazor连接建立后才渲染内容。

总结

推荐使用方案2,将令牌存储在服务器端,既规避JS互操作限制,也提升安全性(令牌不暴露给客户端)。若必须使用客户端存储,则采用方案1确保HttpClient调用在JS环境就绪后执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:55:15