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

Blazor WebAssembly单例状态容器服务在新标签页组件中无法获取数据的问题

Blazor WebAssembly单例状态容器服务在新标签页组件中无法获取数据的问题

这个问题其实是Blazor WebAssembly的运行机制导致的,我来给你拆解下原因和解决方案:

问题根源

Blazor WebAssembly应用在每个浏览器标签页中都是独立的运行实例——哪怕你用AddSingleton注册服务,这个“单例”也只限于当前标签页的应用实例。当你打开新标签页时,浏览器会重新加载整个Blazor应用,初始化全新的服务实例,所以新标签页里的ServiceScopped自然是空的,和原标签页的服务完全不共享数据。

解决方案:用浏览器本地存储(localStorage)实现跨标签页数据同步

要让数据在多个标签页间共享,我们需要把状态持久化到浏览器的本地存储(localStorage)里,它是浏览器级别的存储,所有同域名的标签页都能访问。同时还要监听存储变化事件,确保一个标签页修改数据后,其他标签页能自动更新状态。

1. 修改状态服务,加入本地存储支持

我们需要给ServiceScopped注入IJSRuntime来操作localStorage,同时添加存储变化的监听:

using Microsoft.JSInterop;
using System.Text.Json;

public class ServiceScopped : IDisposable
{
    private readonly IJSRuntime _jsRuntime;
    public DataDTO Data { get; private set; } = new DataDTO();
    public event Action? Change;

    public ServiceScopped(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
        // 初始化时从localStorage加载数据
        _ = LoadDataFromLocalStorageAsync();
        // 监听其他标签页的存储变化
        _ = ListenForStorageChangesAsync();
    }

    public async Task UpdateData(DataDTO newData)
    {
        Data = newData;
        // 保存到localStorage
        await SaveDataToLocalStorageAsync(newData);
        NotifyStateChanged();
    }

    private async Task SaveDataToLocalStorageAsync(DataDTO data)
    {
        var json = JsonSerializer.Serialize(data);
        await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "serviceData", json);
    }

    private async Task LoadDataFromLocalStorageAsync()
    {
        var json = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "serviceData");
        if (!string.IsNullOrEmpty(json))
        {
            Data = JsonSerializer.Deserialize<DataDTO>(json)!;
            NotifyStateChanged();
        }
    }

    private async Task ListenForStorageChangesAsync()
    {
        await _jsRuntime.InvokeVoidAsync("addEventListener", "storage", 
            DotNetObjectReference.Create(this).InvokeMethodAsync<string>("OnStorageChanged"));
    }

    [JSInvokable]
    public void OnStorageChanged()
    {
        // 存储变化时重新加载数据
        _ = LoadDataFromLocalStorageAsync();
    }

    private void NotifyStateChanged() => Change?.Invoke();

    public void Dispose()
    {
        _ = _jsRuntime.InvokeVoidAsync("removeEventListener", "storage", 
            DotNetObjectReference.Create(this).InvokeMethodAsync<string>("OnStorageChanged"));
    }
}

2. 调整Program.cs的服务注册

确保服务能注入IJSRuntime,注册方式不变,注意服务类已实现IDisposable:

builder.Services.AddSingleton<ServiceScopped>();

3. 调整组件代码

主组件的Save方法无需大改,现在UpdateData会自动把数据存到localStorage:

@inject ServiceScopped ServiceScopped
@inject IJSRuntime jsRuntime
@implements IDisposable

<EditForm OnSubmit="@HandleSubmit">
    <!-- 表单内容 -->
</EditForm>

@code {
    protected override void OnInitialized()
    {
        ServiceScopped.Change += StateHasChanged;
    }

    private async Task HandleSubmit()
    {
        await Save();
    }

    private async Task Save()
    {
        var serviceDTO = new DataDTO 
        {
            // 赋值表单属性
        };
        await ServiceScopped.UpdateData(serviceDTO);
  
        await jsRuntime.InvokeVoidAsync("open", "newwindows", "_blank");
    }

    public void Dispose()
    {
        ServiceScopped.Change -= StateHasChanged;
    }
}

新标签页的组件初始化时,会自动从localStorage加载最新数据:

@inject ServiceScopped ServiceScopped
@implements IDisposable

<table>
    <!-- 渲染ServiceScopped.Data的内容 -->
</table>

@code{
    protected override void OnInitialized()
    {
        ServiceScopped.Change += StateHasChanged;
    }

    public void Dispose()
    {
        ServiceScopped.Change -= StateHasChanged;
    }
}

额外说明

  • 确保DataDTO是可序列化的(比如没有循环引用,属性均为公共可访问)。
  • 如果数据敏感,不要用localStorage,可考虑其他安全存储方案;sessionStorage是标签页隔离的,不适合跨标签页共享。
  • storage事件只会在其他标签页修改存储时触发当前标签页的回调,当前标签页修改不会触发,这符合我们的同步需求。

备注:内容来源于stack exchange,提问作者Hale Bopp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 08:53:04