.NET 8 Blazor Server初始化异步加载界面卡顿问题求助
问题描述
我在新项目中使用.NET 8 Blazor Server/Client,基于Sync Fusion模板开发并集成了ASP.NET Identity功能。制作了一个展示服务器日志的Blazor Server页面,登录后进入该页面时,期望数据加载期间显示“Loading Seri Logs......”,但实际是数据库请求完成后才显示加载提示,还会重复触发数据库调用,最终才展示带数据的Grid。即使替换数据库调用为Task.Delay(1000),问题依旧。原本以为Blazor会在数据加载完成前显示加载状态,完成后再展示Grid,请问哪里操作有误?
模板项目中其他无异步调用的页面加载正常。
Seri-Log.Razor页面
@page "/maintenance/seri-log-server" @using Microsoft.AspNetCore.Authorization @using App.Client.Services @using System.Net.Http.Headers @using App.Shared.DTOs.SeriLog @using App.Shared.Interfaces @using Syncfusion.Blazor.Buttons @using System.Text.Json @using Syncfusion.Blazor.Grids @using System.Net.Http.Json @inject ILogger<SeriLogsServer> _logger; @inject IMaintenanceStore maintenanceStore; @attribute [Authorize] <PageTitle>Seri Log Maintenance Server</PageTitle> <h3>Start Local: @CurrenTimeLocalStart</h3> <hr /> @if (Error != null) { <div class="c-error"> <h3>Error</h3> <h5>@Error</h5> </div> } @if (SeriLogs == null) { <span> Loading Seri Logs...... </span> } else { <hr /> <h3>End Local: @CurrenTimeLocalEnd</h3> <SfGrid ID="Grid" DataSource="@SeriLogs" @ref="Grid" AllowPaging="true" AllowFiltering="true" AllowReordering="true" AllowResizing="true" AllowGrouping="true" AllowExcelExport="true" AllowSelection="false"> <GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.FilterBar"></GridFilterSettings> <GridPageSettings PageSizes="true"></GridPageSettings> <GridSelectionSettings Type="Syncfusion.Blazor.Grids.SelectionType.Multiple"></GridSelectionSettings> <GridColumns> <GridColumn Field=@nameof(SeriLogDTO.Id) HeaderText="ID" IsPrimaryKey="true" TextAlign="TextAlign.Center" Width="120"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.TimeStamp) HeaderText="TimeStamp" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="120"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.Message) HeaderText="Message" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="500"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.Exception) HeaderText="Exception" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="200"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.MessageTemplate) HeaderText="MessageTemplate" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="300"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.Level) HeaderText="Level" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="200"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.Properties) HeaderText="Properties" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="200"></GridColumn> <GridColumn Field=@nameof(SeriLogDTO.LogEvent) HeaderText="LogEvent" IsPrimaryKey="false" TextAlign="TextAlign.Center" Width="200"></GridColumn> </GridColumns> </SfGrid> } <style> .c-error { padding: 5px; background: #fff0f4; color: #c51244; } </style> @code { SfGrid<SeriLogDTO>? Grid; private DateTime CurrenTimeLocalStart { get; set; } private DateTime CurrenTimeLocalEnd { get; set; } public IList<SeriLogDTO>? SeriLogs = null; public string Error = null; private bool isInitialized = false; protected override async Task OnInitializedAsync() { CurrenTimeLocalStart = DateTime.UtcNow.ToLocalTime(); // SeriLogs = await maintenanceStore.ListRelated(); await Task.Delay(2000); CurrenTimeLocalEnd = DateTime.UtcNow.ToLocalTime(); } }
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="App.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> <link rel="stylesheet" href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" /> @* <link rel="stylesheet" href="_content/Syncfusion.Blazor.Themes/fluent.css" /> *@ <script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js" type="text/javascript"></script> <script src="js/localStorage.js"></script> <script src="js/logoutOnly.js"></script> </head> <body> <Routes /> <script src="_framework/blazor.web.js" autostart="true"></script> @* <script src="js/boot.js"></script> *@ <script> // Wait until a 'reload' button appears new MutationObserver((mutations, observer) => { if (document.querySelector('#components-reconnect-modal h5 a')) { // Now every 10 seconds, see if the server appears to be back, and if so, reload async function attemptReload() { await fetch(''); // Check the server really is back location.reload(); } observer.disconnect(); attemptReload(); setInterval(attemptReload, 10000); } }).observe(document.body, { childList: true, subtree: true }); </script> </body> </html> <script> function isDevice() { return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(navigator.userAgent); } </script>
Route.razor
@using App.Components.Layout @* <Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> </Router> *@ <Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <NotAuthorized> <App.Components.Pages.Account.Login /> </NotAuthorized> </AuthorizeRouteView> </Found> <NotFound> <CascadingAuthenticationState> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </CascadingAuthenticationState> </NotFound> </Router>
解决方案
问题根源
- 初始化阶段阻塞渲染:
OnInitializedAsync属于组件初始化生命周期环节,Blazor Server会等待该方法完全执行完毕后才会进行第一次组件渲染。因此你在该方法内的延迟操作会导致页面完全停滞,直到方法执行结束才会输出任何内容,包括加载提示。 - 未更新组件状态:当前代码中既没有给
SeriLogs赋值(实际数据获取被注释),也没有触发组件状态更新的逻辑,导致组件无法感知数据加载完成的状态变化。 - 重复加载触发:组件可能因路由跳转、框架内部渲染机制或Syncfusion Grid特性触发多次初始化,进而导致重复的数据请求。
修复步骤
1. 调整异步加载时机
改用OnAfterRenderAsync执行数据加载,该方法会在组件第一次渲染完成后触发,此时加载提示已经显示在页面上,用户能及时看到反馈。
2. 添加加载状态标志
新增isLoading变量控制加载提示的显示,同时避免重复触发数据请求。
3. 正确更新状态并触发渲染
数据加载完成后给SeriLogs赋值,并手动调用StateHasChanged()触发组件重新渲染。
修改后的Seri-Log.Razor关键代码:
@* 页面部分调整加载判断逻辑 *@ @if (Error != null) { <div class="c-error"> <h3>Error</h3> <h5>@Error</h5> </div> } else if (isLoading) { <span>Loading Seri Logs......</span> } else { <hr /> <h3>End Local: @CurrenTimeLocalEnd</h3> <!-- 原Grid代码保留 --> } @code { // 新增加载状态标志 private bool isLoading = true; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { CurrenTimeLocalStart = DateTime.UtcNow.ToLocalTime(); try { await Task.Delay(2000); // 替换为实际数据获取逻辑 SeriLogs = await maintenanceStore.ListRelated(); CurrenTimeLocalEnd = DateTime.UtcNow.ToLocalTime(); } catch (Exception ex) { Error = ex.Message; } finally { isLoading = false; StateHasChanged(); // 手动触发组件渲染更新 } } } }
额外说明
如果坚持使用OnInitializedAsync,可以在异步操作前手动调用StateHasChanged(),但这种方式在Blazor Server中无法保证加载提示优先显示,因为初始化阶段的渲染会等待异步方法完成,因此更推荐使用OnAfterRenderAsync的方案。
内容的提问来源于stack exchange,提问作者tvbRPTI2754
相关产品推荐
相关产品推荐

