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

Blazor 8静态SSR下Auto模式如何持久化用户状态(非Singleton)

Blazor Web App Auto模式下用户状态持久化(非Singleton方案)

问题背景

我有一个Blazor Web App,交互模式设置为Auto且按页面/组件配置位置。由于App、Routes和Layouts在服务器端静态渲染,每次导航都会重新注入注册为Scoped的BrowserInfo。需要在不使用Singleton服务的前提下,实现Auto渲染模式下的状态持久化,确保状态仅归属单个用户。核心需求是根据iframe检测结果在Routes.razor中设置布局,且项目无法使用全局交互性(如Blazor Server)。

当前实现代码

App.razor

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="BlazorAppAuto.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />

    <script>
        if (window.self !== window.top) {
            sessionStorage.setItem('iframe', 'true');
        }
        else {
            // 注:此处逻辑错误,应设置为'false'
            sessionStorage.setItem('iframe', 'true');
        }

        const iframeValue = sessionStorage.getItem('iframe');
        const url = new URL(window.location);
        if (url.searchParams.get('iframe') !== iframeValue) {
            url.searchParams.set('iframe', iframeValue);
            window.location.href = url.toString();
        }
    </script>
    <HeadOutlet />
</head>
<body>
    <Routes />
</body>
</html>

Routes.razor

<Router AppAssembly="typeof(Program).Assembly"
        AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly, typeof(BasketApp.UI.DependencyInjection).Assembly }">
    <Found Context="routeData">
        <CascadingValue Value="routeData" >
            <AuthorizeRouteView RouteData="routeData" DefaultLayout="BrowserInfo.IsIframe ? typeof(FrameLayout) : typeof(MainLayout)">
            <NotAuthorized>
                @if (context.User.Identity?.IsAuthenticated is not true)
                {
                    // <RedirectToLogin />
                }
                else
                {
                    <BasketApp.UI.Components.Common.ErrorsAlert 
                        Errors="@([ErrorOr.Error.Forbidden(description: "You are not authorized to access this resource.")])" />
                }
            </NotAuthorized>
            <Authorizing>
                Authorizing...
            </Authorizing>
        </AuthorizeRouteView>
            <FocusOnNavigate RouteData="routeData" Selector="h1" />
        </CascadingValue>
    </Found>
</Router>
@code {
    [SupplyParameterFromQuery(Name = "iframe")] public bool IFrame { get; set; }
    @inject BrowserInfo BrowserInfo;

    protected override void OnInitialized()
    {
        if (BrowserInfo.IsIframe) return;
        BrowserInfo.IsIframe = IFrame;
        base.OnInitialized();
    }
}

备选方案:MainLayout中控制布局

@inject BrowserInfo BrowserInfo

@if (!BrowserInfo.IsIframe)
{
    <NavMenu @rendermode=PreferredRenderMode />
}

<!-- 布局其余内容 -->

@code {
    [SupplyParameterFromQuery(Name = "iframe")] public bool IFrame { get; set; }
    @inject BrowserInfo BrowserInfo;

    protected override void OnInitialized()
    {
        if (BrowserInfo.IsIframe) return;
        BrowserInfo.IsIframe = IFrame;
        base.OnInitialized();
    }
}

可行解决方案

方案1:Cookie持久化+Scoped服务读取

服务器端静态渲染时无法直接访问浏览器SessionStorage,因此将iframe检测结果存入Cookie,让Scoped服务从当前用户的Cookie中读取状态,天然保证用户隔离:

  1. 修正App.razor脚本,存入Cookie
<script>
    const isInIframe = window.self !== window.top;
    // 设置会话级Cookie,同站点策略避免跨域问题
    document.cookie = `IsInIframe=${isInIframe}; path=/; SameSite=Lax`;
    
    // 同步URL参数(可选,保持URL与状态一致)
    const url = new URL(window.location);
    const currentParam = url.searchParams.get('iframe') === 'true';
    if (currentParam !== isInIframe) {
        url.searchParams.set('iframe', isInIframe.toString());
        window.location.href = url.toString();
    }
</script>
  1. 改造BrowserInfo服务,从Cookie读取
public class BrowserInfo
{
    public bool IsIframe { get; }

    public BrowserInfo(IHttpContextAccessor httpContextAccessor)
    {
        var cookieValue = httpContextAccessor.HttpContext?.Request.Cookies["IsInIframe"];
        if (bool.TryParse(cookieValue, out var isIframe))
        {
            IsIframe = isIframe;
        }
    }
}
  1. 注册依赖服务
// Program.cs中添加
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<BrowserInfo>();

方案2:直接通过URL参数控制布局(无状态依赖)

去掉对BrowserInfo服务的依赖,直接在Routes.razor中读取URL参数切换布局,无需维护状态:

<Router AppAssembly="typeof(Program).Assembly"
        AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly, typeof(BasketApp.UI.DependencyInjection).Assembly }">
    <Found Context="routeData">
        <CascadingValue Value="routeData" >
            <AuthorizeRouteView RouteData="routeData" 
                                DefaultLayout="@(IFrame ? typeof(FrameLayout) : typeof(MainLayout))">
                <NotAuthorized>
                    @if (context.User.Identity?.IsAuthenticated is not true)
                    {
                        // <RedirectToLogin />
                    }
                    else
                    {
                        <BasketApp.UI.Components.Common.ErrorsAlert 
                            Errors="@([ErrorOr.Error.Forbidden(description: "You are not authorized to access this resource.")])" />
                    }
                </NotAuthorized>
                <Authorizing>
                    Authorizing...
                </Authorizing>
            </AuthorizeRouteView>
            <FocusOnNavigate RouteData="routeData" Selector="h1" />
        </CascadingValue>
    </Found>
</Router>
@code {
    [SupplyParameterFromQuery(Name = "iframe")] public bool IFrame { get; set; }
}

这种方式无需服务状态,完全依赖URL参数传递信息,服务器端渲染时直接读取参数即可,天然保证每个用户的状态独立。

方案3:客户端交互式组件维护状态

对于Auto模式下已激活交互的客户端组件,可通过JS互操作从SessionStorage读取状态,同时服务器端初始渲染仍依赖URL/Cookie传递值:

  1. App.razor脚本添加全局变量
<script>
    window.isInIframe = window.self !== window.top;
    sessionStorage.setItem('isInIframe', window.isInIframe.toString());
</script>
  1. 改造BrowserInfo服务支持客户端读取
public class BrowserInfo
{
    private readonly IJSRuntime _jsRuntime;
    private bool? _cachedValue;

    public BrowserInfo(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    public async Task<bool> GetIsIframeAsync()
    {
        if (_cachedValue.HasValue) return _cachedValue.Value;
        _cachedValue = await _jsRuntime.InvokeAsync<bool>("window.isInIframe");
        return _cachedValue.Value;
    }
}

客户端组件可调用GetIsIframeAsync()获取状态,服务器端静态渲染时仍需通过URL或Cookie传递初始值以保证布局正确。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 17:57:34