Blazor Server(.NET8)重载页面触发NullReferenceException求助
问题场景
我正在排查Blazor Server(.NET 8)应用的页面重载异常问题:应用头部有一个国家选择下拉框,切换选项后需要重载整个页面。尝试调用NavigationManager.NavigateTo(NavigationManager.Uri, true, true)实现重载,页面确实刷新了,但随后抛出NullReferenceException,且异常未指向业务代码。用await JavaScriptRuntime.InvokeVoidAsync("location.reload")替代也会出现同样异常,但手动刷新页面(地址栏回车)时一切正常。在Visual Studio模板新项目中无法复现该问题,即使引入了实际项目的ProtectedLocalStorage也正常。
异常详情
System.NullReferenceException: Object reference not set to an instance of an object.
调用栈:
Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult) Line 201 C#
Microsoft.JSInterop.dll!Microsoft.JSInterop.Infrastructure.DotNetDispatcher.EndInvokeDotNetAfterTask(System.Threading.Tasks.Task task, Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo) Line 164 C#
Microsoft.JSInterop.dll!Microsoft.JSInterop.Infrastructure.DotNetDispatcher.BeginInvokeDotNet.AnonymousMethod__0(System.Threading.Tasks.Task t) Line 124 C#
System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread threadPoolThread, System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Line 264 C#
System.Private.CoreLib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot, System.Threading.Thread threadPoolThread) Line 2349 C#
System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Line 913 C#
System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() Line 102 C#
问题分析
从调用栈可以看出,异常发生在RemoteJSRuntime.EndInvokeDotNet方法的else分支中,推测是_clientProxy实例为空导致。手动刷新正常但代码触发重载异常,说明代码触发的重载过程中,存在未完成的JS互操作调用,在页面刷新后这些调用的回调无法找到有效的电路(Circuit)实例,从而引发空引用。
排查与解决步骤
1. 捕获更详细的异常信息
- 启用详细日志:在
appsettings.json中添加日志配置,记录Microsoft.AspNetCore.Components.Server和Microsoft.JSInterop的Debug级日志,查看是否有未完成的JS调用记录:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Microsoft.AspNetCore.Components.Server": "Debug", "Microsoft.JSInterop": "Debug" } } }
- 全局异常捕获:在
_Host.cshtml中使用ErrorBoundary组件包裹内容,尝试捕获更具体的异常上下文:
<ErrorBoundary> <ChildContent> @Body </ChildContent> <ErrorContent> <p>未处理异常:@context.Message</p> <p>栈跟踪:@context.StackTrace</p> </ErrorContent> </ErrorBoundary>
2. 重载前终止未完成的JS互操作
在调用页面重载前,释放所有JS模块引用并确保异步操作完成:
private IJSObjectReference? _jsModule; private async Task OnCountryChanged(string countryCode) { // 保存国家设置逻辑... // 释放JS模块引用 if (_jsModule != null) { await _jsModule.DisposeAsync(); _jsModule = null; } // 短暂延迟确保所有异步操作收尾 await Task.Delay(100); NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); }
3. 跟踪组件生命周期,取消无效回调
在组件中使用CancellationToken跟踪生命周期,销毁时取消所有异步操作,避免组件卸载后触发JS调用:
private CancellationTokenSource _cts = new(); protected override void Dispose(bool disposing) { if (disposing) { _cts.Cancel(); _cts.Dispose(); } base.Dispose(disposing); } private async Task SomeAsyncOperation() { try { await JSRuntime.InvokeVoidAsync("someJsFunction", _cts.Token); } catch (OperationCanceledException) { // 忽略组件销毁时的取消操作 } }
4. 替代重载方案
如果上述方法无效,尝试通过表单提交触发页面重载,避免JS互操作残留问题:
<form @ref="formRef" method="get" action="@NavigationManager.Uri"> <select @onchange="OnCountryChanged"> <!-- 国家选项 --> </select> </form>
对应的处理逻辑:
private ElementReference formRef; private async Task OnCountryChanged(ChangeEventArgs e) { // 保存国家设置逻辑... await formRef.SubmitAsync(); }
内容的提问来源于stack exchange,提问作者gis

