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

Blazor Server应用直接访问内部页面时OnAfterRenderAsync不触发

Blazor Server 直接访问内部页面时 CascadingAppState 的 OnAfterRenderAsync 不触发问题

问题背景

Blazor Server端应用采用CascadingAppState传递凭据,从首页(http://localhost)启动应用功能正常,但直接访问内部页面(如http://localhost/products/1785)时,CascadingAppState.razor中的OnAfterRenderAsync事件完全不触发;从首页跳转至该内部页面则无此问题。已尝试调整blazor.js标签位置,未解决问题。

核心代码

CascadingAppState.razor

@implements IAppState
@inject ILocalStorageService LocalStorageService
@using CommercialSalesPortal.Models
@inject Radzen.DialogService dialogService
@inject NavigationManager NavManager
<CascadingValue Value="this">
    @ChildContent
</CascadingValue>
@code {

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private string Caller = "";

    public int TimeToLiveInSeconds { get; set; } = 60;
    public DateTime LastAccessed { get; set; } = DateTime.Now;

    public void SetProperty(ComponentBase caller, string PropertyName,
                            object PropertyValue, bool SaveChanges = true)
    {
        try
        {
            var prop = this.GetType().GetProperty(PropertyName);
            if (prop != null)
            {
                this.Caller = caller.GetType().Name;
                prop.SetValue(this, PropertyValue);
                if (SaveChanges)
                    this.SaveChanges();
                StateHasChanged();
            }
        }
        catch (Exception ex)
        {

        }
    }

    private string message = "";
    public string Message
    {
        get => message;
        private set
        {
            message = value;
            if (Caller == "Toolbar")
            {
                // The Toolbar component changed the AppState Message property
            }
        }
    }

    private bool enabled = false;
    public bool Enabled
    {
        get => enabled;
        private set
        {
            enabled = value;
        }
    }

    private int counter = 0;
    public int Counter
    {
        get => counter;
        private set
        {
            counter = value;
            if (Caller == "Counter")
            {
                // The Counter page changed the AppState Counter property
            }
        }
    }
    private List<PriceRequestAccountUsage> usagelist = null;
    public List<PriceRequestAccountUsage> UsageList { get; set; }

    private UserObject currentuser = null;
    public UserObject CurrentUser
    {
        get => currentuser;
        private set
        {
            currentuser = value;
        }
    }
    private string encryptedcreidentials = "";
    public string EncryptedCredentials
    {
        get => encryptedcreidentials;
        private set
        {
            encryptedcreidentials = value;
            if (Caller == "Toolbar")
            {
                // The Toolbar component changed the AppState Message property
            }
        }
    }
    private async void DialogPopupClosed(dynamic theResult)
    {
        if (theResult != null && theResult == true)
        {
            //NavManager.NavigateTo("index");
            await InvokeAsync(StateHasChanged);
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender) // do this only once
        {
            // Get the JSON data
            var json = await LocalStorageService.GetItemAsStringAsync("AppStateJson");
            dialogService.OnClose += DialogPopupClosed;
            if (json != null && json != "")
            {
                // Deserialize to AppStateData
                var appStateData = JsonSerializer.Deserialize<AppStateData>(json);
                // Are we within the time window?
                if (DateTime.Now <= appStateData.LastAccessed
                    .AddDays(appStateData.TimeToLiveInSeconds))
                {
                    // Yes! use our own SetProperty method to set our property values
                    var properties = appStateData.GetType().GetProperties();
                    foreach (var property in properties)
                    {
                        var type = property.PropertyType;
                        var value = property.GetValue(appStateData);
                        SetProperty(this, property.Name, value, false);
                    }
                }
                // Re-render
                if (!string.IsNullOrEmpty(EncryptedCredentials))
                {
                    var splitCredentials = CommercialSalesPortal.Web.Code.EncryptionFactory.DecryptString(EncryptedCredentials).Split("|");
                    CurrentUser = CPS.Data.Code.UserFactory.ValidateUser(splitCredentials[0], splitCredentials[1]);

                    EncryptedCredentials = CommercialSalesPortal.Web.Code.EncryptionFactory.EncryptString("JoshAdmin|Password123!|" + DateTime.Now);
                    SetProperty(this, "EncryptedCredentials", EncryptedCredentials);
                    await InvokeAsync(StateHasChanged);
                }
                else
                {
                    //CurrentUser = CPS.Data.Code.UserFactory.ValidateUser("JoshAdmin", "Password123!");
                    //await InvokeAsync(StateHasChanged);
                    PopLogin();
                }
            }
            else
            {
                PopLogin();
            }
        }
    }
    public void PopLogin()
    {

        SetProperty(this, "CurrentUser", new CommercialSalesPortal.Models.UserObject());
        dialogService.Open<CPS.Web.Pages.LogIn>("LogIn", null, new Radzen.DialogOptions() { Width = "800px", Height = "570px", Resizable = true, Draggable = true });
        StateHasChanged();
    }

    public void SaveChanges()
    {
        LastAccessed = DateTime.Now;
        var appstate = (IAppState)this;
        var json = System.Text.Json.JsonSerializer.Serialize(appstate);
        LocalStorageService.SetItemAsStringAsync("AppStateJson", json);
    }
}

MainLayout.razor

@inherits LayoutComponentBase
@inject Radzen.DialogService dialogService
@inject Radzen.NotificationService notificationService
@inject IJSRuntime JS
@inject NavigationManager NavManager
@inject Web.Data.ICookieFactory cookieFactory

@using CPS.Web.Code
<div class="page">
    <div class="content">
        <RadzenDialog />
        <RadzenNotification />
        @if (AppState != null && AppState.CurrentUser != null && AppState.CurrentUser.UserProfileKey > 0)
        {
        }
    </div>
</div>
@code
{
    [CascadingParameter] public CascadingAppState AppState { get; set; }
    private string JumpToRequestNumber = string.Empty;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    { }
    protected override void OnInitialized()
    {
        dialogService.OnClose += ReloadPage;
    }
    public void ReloadPage(dynamic theResult)
    {
        StateHasChanged();
    }
    private void RefreshMe()
    {
        if (AppState.CurrentUser != null)
        {
            JS.InvokeAsync<object>("SetActiveNavTab");
            CreateUserCookie();
            StateHasChanged();
        }
    }
    public void PopSettings()
    {
        dialogService.Open<CPS.Web.Pages.Settings>("Settings", null, new Radzen.DialogOptions() { Width = "800px", Height = "570px", Resizable = true, Draggable = true });
    }
    public void PopLogin()
    {
        dialogService.Open<CPS.Web.Pages.LogIn>("LogIn", null, new Radzen.DialogOptions() { Width = "800px", Height = "570px", Resizable = true, Draggable = true });
        StateHasChanged();
    }
    private void JumpToRequest()
    {
        NavManager.NavigateTo("/PriceRequest/" + JumpToRequestNumber);
    }
    private void PopBugReport()
    {
        dialogService.Open<CPS.Web.Pages.Popup.ReportBug>("Report Bug", null,
        new Radzen.DialogOptions() { Width = "700px", Height = "650px" });
    }
    private void SayThanks()
    {
        try
        {
            CommercialSalesPortal.Web.Code.EmailFactory.SendEmail(CommercialSalesPortal.Code.Constants.ThanksEmailReportTo, GenerateMessage(), "New Thank You", CPS.Entity.Constants.EmailFromAddress, string.Empty);
            NotificationMessage theMesage = new NotificationMessage { Style = "position: absolute; left: -1000px;", Severity = NotificationSeverity.Success, Summary = "Your thanks was sent successully!", Detail = "Much Appreciated", Duration = 4000 };
            notificationService.Notify(theMesage);
        }
        catch (Exception ex)
        {
            NotificationMessage theMesage = new NotificationMessage { Style = "position: absolute; left: -1000px;", Severity = NotificationSeverity.Error, Summary = "There was an error sending your thanks: ", Detail = ex.Message, Duration = 40000 };
            notificationService.Notify(theMesage);
        }
    }
    private string GenerateMessage()
    {
        return "A new thanks message was sent from " + AppState.CurrentUser.DisplayName;
    }
    /// <summary>
    /// Sets values for the price request cookie
    /// </summary>
    private void WritePricingCookie()
    {
        LocalFunctions.ConfigureDependencyInjections(cookieFactory);
        LocalFunctions.WriteDataToCookie(Entity.Constants.PriceRequestCookieName, JumpToRequestNumber);
    }
}

解决思路

1. 迁移初始化逻辑至OnInitializedAsync

OnAfterRenderAsync依赖组件渲染完成,直接访问内部页面时,路由页面可能在CascadingAppState渲染前就已加载,导致firstRender逻辑未触发。将初始化逻辑移至OnInitializedAsync,该方法在组件初始化阶段必执行,不受渲染顺序影响:

protected override async Task OnInitializedAsync()
{
    var json = await LocalStorageService.GetItemAsStringAsync("AppStateJson");
    dialogService.OnClose += DialogPopupClosed;
    if (json != null && json != "")
    {
        var appStateData = JsonSerializer.Deserialize<AppStateData>(json);
        if (DateTime.Now <= appStateData.LastAccessed.AddDays(appStateData.TimeToLiveInSeconds))
        {
            var properties = appStateData.GetType().GetProperties();
            foreach (var property in properties)
            {
                var value = property.GetValue(appStateData);
                SetProperty(this, property.Name, value, false);
            }
        }
        if (!string.IsNullOrEmpty(EncryptedCredentials))
        {
            var splitCredentials = CommercialSalesPortal.Web.Code.EncryptionFactory.DecryptString(EncryptedCredentials).Split("|");
            CurrentUser = CPS.Data.Code.UserFactory.ValidateUser(splitCredentials[0], splitCredentials[1]);

            EncryptedCredentials = CommercialSalesPortal.Web.Code.EncryptionFactory.EncryptString("JoshAdmin|Password123!|" + DateTime.Now);
            SetProperty(this, "EncryptedCredentials", EncryptedCredentials);
        }
        else
        {
            PopLogin();
        }
    }
    else
    {
        PopLogin();
    }
}

2. 确认级联值的全局覆盖范围

确保CascadingAppState作为应用根组件包裹整个Router,保证所有路由页面都能接收级联值。检查App.razor结构:

<CascadingAppState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAppState>

3. 处理预渲染的影响

Blazor Server默认预渲染,直接访问内部页面时,服务器预渲染阶段无法访问LocalStorage,导致初始化逻辑失效。可通过以下方式处理:

  • 跳过预渲染阶段:注入IHttpContextAccessor,判断上下文为空时跳过初始化
@inject IHttpContextAccessor HttpContextAccessor

protected override async Task OnInitializedAsync()
{
    if (HttpContextAccessor.HttpContext == null) return;
    // 原初始化逻辑
}
  • 全局禁用预渲染:在项目文件中添加配置
<PropertyGroup>
    <DisableBlazorServerPrerendering>true</DisableBlazorServerPrerendering>
</PropertyGroup>

4. 确保LocalStorage在客户端上下文访问

直接访问内部页面时,Blazor初始化未完成可能导致LocalStorage访问失败,将LocalStorage操作包裹在InvokeAsync中,确保在客户端执行:

var json = await InvokeAsync(() => LocalStorageService.GetItemAsStringAsync("AppStateJson"));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 19:01:12