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
相关产品推荐
相关产品推荐

