Blazor Server中全局捕获未处理异常并获取浏览器当前URL的实现方案
全局捕获Blazor Server未处理异常并记录当前浏览器URL
我完全理解你的痛点——Blazor Server用SignalR通信后,Serilog请求日志只能拿到初始的/_blazor路径,根本没法定位异常发生在哪个页面路由上。而且手动加try-catch太繁琐,还会破坏代码结构。下面给你几个不用写大量try-catch就能全局捕获异常并拿到当前URL的方案:
方案1:用全局ErrorBoundary直接处理
Blazor的ErrorBoundary是组件级的异常捕获容器,把它套在根组件外面,就能捕获整个应用中组件渲染、事件触发时的未处理异常,同时结合NavigationManager轻松拿到当前浏览器的路由URL。
实现步骤:
打开App.razor,注入NavigationManager和ILogger,用ErrorBoundary包裹整个路由组件,自定义错误处理逻辑:
@inject NavigationManager NavManager @inject ILogger<App> Logger <ErrorBoundary OnError="HandleGlobalError"> <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> </ErrorBoundary> @code { private void HandleGlobalError(Exception exception) { // 拿到当前浏览器的完整URL,包括Blazor路由 var currentPageUrl = NavManager.Uri; // 用Serilog记录异常和URL Logger.LogError(exception, "未处理异常发生在页面: {CurrentUrl}", currentPageUrl); // 这里不用额外处理错误UI,Blazor会自动显示默认的blazor-error-ui // 如果想自定义错误页面,可以在这里添加逻辑 } }
这个方案简单直接,能覆盖绝大多数组件层面的异常,而且完全不需要修改业务代码。
方案2:注册全局异常日志服务(更优雅的全局处理)
如果想让所有ErrorBoundary都统一处理异常日志,可以实现IErrorBoundaryLogger接口,把日志逻辑抽成全局服务,这样不管哪里用ErrorBoundary,都会自动触发日志记录。
实现步骤:
- 创建自定义的异常日志服务:
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.Logging; public class GlobalErrorLogger : IErrorBoundaryLogger { private readonly ILogger<GlobalErrorLogger> _logger; private readonly NavigationManager _navManager; // 构造函数注入需要的服务 public GlobalErrorLogger(ILogger<GlobalErrorLogger> logger, NavigationManager navManager) { _logger = logger; _navManager = navManager; } public ValueTask LogErrorAsync(Exception exception, ErrorBoundaryErrorLogContext context) { var currentUrl = _navManager.Uri; _logger.LogError(exception, "全局未处理异常 | 当前页面: {CurrentUrl}", currentUrl); return ValueTask.CompletedTask; } }
- 在
Program.cs中注册这个服务:
builder.Services.AddScoped<IErrorBoundaryLogger, GlobalErrorLogger>();
- 然后在
App.razor中用CascadingErrorBoundary包裹整个应用(这样所有子组件的ErrorBoundary都会继承这个全局日志逻辑):
<CascadingErrorBoundary> <Router AppAssembly="@typeof(App).Assembly"> <!-- 路由内容和之前一致 --> </Router> </CascadingErrorBoundary>
这个方案的优势是代码解耦,日志逻辑集中管理,后续要修改日志格式或添加其他处理只需要改这一个类。
方案3:处理SignalR连接类的异常(可选)
如果还想捕获SignalR连接中断、电路关闭这类组件之外的异常,可以自定义CircuitHandler来处理:
实现步骤:
- 创建自定义CircuitHandler:
using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.Extensions.Logging; public class CustomCircuitHandler : CircuitHandler { private readonly ILogger<CustomCircuitHandler> _logger; private readonly NavigationManager _navManager; public CustomCircuitHandler(ILogger<CustomCircuitHandler> logger, NavigationManager navManager) { _logger = logger; _navManager = navManager; } public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken) { var currentUrl = _navManager.Uri; _logger.LogWarning("SignalR连接中断 | 当前页面: {CurrentUrl}", currentUrl); return base.OnConnectionDownAsync(circuit, cancellationToken); } public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken) { var currentUrl = _navManager.Uri; _logger.LogInformation("用户会话关闭 | 当前页面: {CurrentUrl}", currentUrl); return base.OnCircuitClosedAsync(circuit, cancellationToken); } }
- 在
Program.cs注册:
builder.Services.AddScoped<CircuitHandler, CustomCircuitHandler>();
关键提示
NavigationManager.Uri是核心:它会同步浏览器的当前路由,返回的是用户实际看到的URL(比如/counter),而不是SignalR的/_blazor路径,完全满足你的需求。- 这些方案都不会影响Blazor默认的
blazor-error-ui显示,异常发生后用户依然能看到默认的错误提示。 - 不需要在任何业务组件中写try-catch,全局处理就能覆盖绝大多数场景。
内容的提问来源于stack exchange,提问作者Dejan Dular




