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

Blazor中DelegatingHandler无法访问LocalStorageService的问题咨询

问题:Blazor中DelegatingHandler无法访问LocalStorage的解决方案

问题背景

使用Blazored.LocalStorage存储API登录返回的JWT令牌,转用Refit库重构HTTP客户端时,通过DelegatingHandler自动为请求添加Bearer令牌,出现LocalStorage访问异常。已全局禁用预渲染,组件的OnInitializedAsync中可正常使用LocalStorage,此前硬编码客户端手动添加令牌的逻辑也能正常运行。

AuthenticationHandler 代码

internal sealed class AuthenticationHandler(ILocalStorageService storageService) : DelegatingHandler
{
    private readonly ILocalStorageService _storageService = storageService;

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authToken = await _storageService.GetItemAsStringAsync("test", cancellationToken);

        if (!string.IsNullOrEmpty(authToken))
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", authToken);

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

Refit客户端注册代码

builder.Services.AddRefitClient<ISomeRefitClientInterface>(refitSettings)
    .ConfigureHttpClient(options =>
    {
        options.BaseAddress = new Uri(config["APIs:MainAPI"]!);
        options.Timeout = TimeSpan.FromMinutes(5);
    })
    .AddHttpMessageHandler<AuthenticationHandler>()
    .AddHttpMessageHandler<LoggingHandler>()
    .AddHttpMessageHandler<RetryHandler>();

错误信息

JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

解决方案

核心原因是:DelegatingHandler在服务注册阶段就被实例化,此时Blazor的JavaScript互操作环境尚未就绪,而Blazored.LocalStorage依赖JS互操作,只能在组件渲染完成后的浏览器环境中正常调用。以下是两种可行的解决办法:

方案1:内存缓存令牌+状态容器同步

  1. 创建内存状态容器用于缓存令牌
public class AuthTokenState
{
    public string? Token { get; set; }
}
  1. 注册为Scoped服务
builder.Services.AddScoped<AuthTokenState>();
  1. 登录成功后同步更新LocalStorage和内存容器
// 登录组件内的登录逻辑
private async Task Login()
{
    var token = await _authApi.Login(loginModel);
    await _localStorage.SetItemAsStringAsync("test", token);
    _authTokenState.Token = token;
}
  1. 修改AuthenticationHandler优先使用内存令牌,缺失时再从LocalStorage加载并同步
internal sealed class AuthenticationHandler(ILocalStorageService storageService, AuthTokenState authTokenState) : DelegatingHandler
{
    private readonly ILocalStorageService _storageService = storageService;
    private readonly AuthTokenState _authTokenState = authTokenState;

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authToken = _authTokenState.Token;

        // 内存无令牌时,从LocalStorage加载并同步到内存
        if (string.IsNullOrEmpty(authToken))
        {
            authToken = await _storageService.GetItemAsStringAsync("test", cancellationToken);
            _authTokenState.Token = authToken;
        }

        if (!string.IsNullOrEmpty(authToken))
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", authToken);

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

第一次请求会加载LocalStorage数据到内存,后续请求直接使用内存令牌,避免重复触发JS互操作。

方案2:确保Handler在浏览器环境中访问LocalStorage

本质和方案1类似,核心是将令牌提前加载到内存中。可以在根组件的OnAfterRenderAsync中完成首次令牌加载,确保JS互操作环境就绪后再初始化Refit客户端的使用场景。

额外注意:Refit客户端默认是Scoped生命周期,要确保Handler依赖的服务(LocalStorage、状态容器)生命周期匹配,避免Singleton服务依赖Scoped服务的问题。

补充说明

之前硬编码客户端在组件基类中添加令牌可行,是因为基类代码在组件生命周期内执行,此时JS互操作环境已经就绪;而DelegatingHandler的初始化时机早于组件渲染,直接调用LocalStorage必然触发异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 12:22:05