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

Blazor UI中展示未捕获异常详情的实现方案问询

解决方案

1. 调整自定义ErrorBoundary,实现错误内容与子内容共存

默认ErrorBoundary捕获异常后会替换子内容,你需要修改渲染逻辑,将错误提示以叠加形式(如固定顶部栏、折叠面板)与正常内容同时展示,而非简单顺序添加(Blazor异常状态下会优先处理错误内容渲染)。

修改CustomErrorBoundary.cs的BuildRenderTree方法:

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    // 先渲染正常业务内容
    builder.AddContent(0, ChildContent);

    // 捕获到异常时,渲染顶部固定的错误提示栏
    if (CurrentException != null && ErrorContent is not null)
    {
        builder.OpenElement(1, "div");
        builder.AddAttribute(2, "style", "position: fixed; top: 0; left: 0; right: 0; background: #dc2626; color: white; padding: 1rem; z-index: 9999;");
        
        // 添加折叠按钮控制详情显示
        builder.OpenElement(3, "button");
        builder.AddAttribute(4, "onclick", EventCallback.Factory.Create(this, ToggleErrorDetails));
        builder.AddAttribute(5, "style", "background: transparent; border: 1px solid white; color: white; margin-left: 1rem;");
        builder.AddContent(6, _showDetails ? "收起详情" : "查看错误详情");
        builder.CloseElement();

        if (_showDetails)
        {
            builder.AddContent(7, ErrorContent(CurrentException));
            // 追加堆栈跟踪显示
            builder.OpenElement(8, "pre");
            builder.AddAttribute(9, "style", "font-size: 0.8rem; margin-top: 1rem; white-space: pre-wrap;");
            builder.AddContent(10, CurrentException.StackTrace ?? "无堆栈跟踪信息");
            builder.CloseElement();
        }
        
        builder.CloseElement();
    }
}

// 控制详情显示状态
private bool _showDetails;
private void ToggleErrorDetails() => _showDetails = !_showDetails;

修改后,异常发生时页面顶部会出现固定错误栏,点击按钮可展开/收起异常消息与堆栈,同时正常内容完全保留。

2. 替换全局默认错误UI(index.html中的提示)

Blazor的全局错误提示无需直接修改index.html静态内容,可通过监听全局错误事件+自定义弹窗覆盖:

步骤1:在index.html末尾添加自定义错误弹窗

<!-- 默认隐藏的错误弹窗 -->
<div id="global-error-modal" style="display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 10000; align-items: center; justify-content: center; flex-direction: column;">
    <div style="background: white; padding: 2rem; border-radius: 8px; max-width: 90%; max-height: 80%; overflow-y: auto;">
        <h3>发生未处理错误</h3>
        <button onclick="hideGlobalError()" style="float: right; border: none; background: #f3f4f6; padding: 0.5rem 1rem; border-radius: 4px;">关闭</button>
        <div class="mt-4">
            <p><strong>错误消息:</strong></p>
            <p id="global-error-message"></p>
        </div>
        <div class="mt-2">
            <p><strong>堆栈跟踪:</strong></p>
            <pre id="global-error-stack" style="font-size: 0.8rem; background: #f3f4f6; padding: 1rem; border-radius: 4px; white-space: pre-wrap;"></pre>
        </div>
        <button onclick="copyGlobalError()" class="mt-4">复制错误详情</button>
    </div>
</div>

<script>
    function showGlobalError(message, stack) {
        document.getElementById('global-error-message').textContent = message;
        document.getElementById('global-error-stack').textContent = stack || '无堆栈信息';
        document.getElementById('global-error-modal').style.display = 'flex';
    }

    function hideGlobalError() {
        document.getElementById('global-error-modal').style.display = 'none';
    }

    function copyGlobalError() {
        const msg = document.getElementById('global-error-message').textContent;
        const stack = document.getElementById('global-error-stack').textContent;
        navigator.clipboard.writeText(`错误消息: ${msg}\n\n堆栈跟踪:\n${stack}`);
        alert('错误详情已复制到剪贴板');
    }

    // 监听全局未捕获错误
    window.addEventListener('unhandledrejection', e => {
        e.preventDefault();
        showGlobalError(e.reason.message, e.reason.stack);
    });

    window.addEventListener('error', e => {
        e.preventDefault();
        showGlobalError(e.message, e.error?.stack);
    });
</script>

步骤2:在Program.cs中配置Blazor全局错误回调

builder.Services.Configure<ErrorBoundaryOptions>(options =>
{
    options.ErrorContent = ex =>
    {
        return builder =>
        {
            // 调用JS显示自定义弹窗
            builder.OpenComponent<GlobalErrorRenderer>(0);
            builder.AddAttribute(1, "Exception", ex);
            builder.CloseComponent();
        };
    };
});

(注:GlobalErrorRenderer是自定义组件,内部通过IJSRuntime调用showGlobalError方法)

3. 分级错误边界,保障网站部分可用性

不要仅在根级(App.razor)添加ErrorBoundary,建议在独立页面、组件级别拆分使用,单个组件出错时不会影响全站功能:

示例:页面级错误边界

<CustomErrorBoundary @ref="pageErrorBoundary">
    <ChildContent>
        <!-- 当前页面的业务组件 -->
        <DataTableComponent />
        <ChartComponent />
    </ChildContent>
    <ErrorContent Context="ex">
        <div class="alert alert-danger p-4">
            <p>当前模块加载失败:</p>
            <p>@ex.Message</p>
            <button @onclick="() => pageErrorBoundary.Recover()" class="mt-2">重试加载</button>
        </div>
    </ErrorContent>
</CustomErrorBoundary>

@code {
    private CustomErrorBoundary pageErrorBoundary;
}

4. 处理API建立前的异常

针对应用启动阶段(API连接前)的异常,可临时存储到浏览器localStorage,待API可用后再上报:

步骤1:在CustomErrorBoundary中添加本地存储逻辑

[Inject]
private IJSRuntime JsRuntime { get; set; }

protected override async Task OnErrorAsync(Exception exception)
{
    // 存储异常到localStorage
    var errorLog = new
    {
        Message = exception.Message,
        StackTrace = exception.StackTrace,
        Timestamp = DateTime.UtcNow.ToString("o")
    };

    var existingLogs = await JsRuntime.InvokeAsync<string>("localStorage.getItem", "blazor-offline-errors");
    var logs = string.IsNullOrEmpty(existingLogs) 
        ? new List<object> { errorLog } 
        : JsonSerializer.Deserialize<List<object>>(existingLogs) ?? new List<object>();
    
    logs.Add(errorLog);
    await JsRuntime.InvokeVoidAsync("localStorage.setItem", "blazor-offline-errors", JsonSerializer.Serialize(logs));

    await base.OnErrorAsync(exception);
}

步骤2:应用启动成功后上报本地日志

在MainLayout.razor中添加上报逻辑:

@inject IJSRuntime JsRuntime
@inject HttpClient HttpClient

@code {
    protected override async Task OnInitializedAsync()
    {
        var offlineLogs = await JsRuntime.InvokeAsync<string>("localStorage.getItem", "blazor-offline-errors");
        if (!string.IsNullOrEmpty(offlineLogs))
        {
            try
            {
                // 调用API上报日志
                await HttpClient.PostAsJsonAsync("/api/error-logs", JsonSerializer.Deserialize<List<object>>(offlineLogs));
                // 上报成功后清空本地存储
                await JsRuntime.InvokeVoidAsync("localStorage.removeItem", "blazor-offline-errors");
            }
            catch (Exception ex)
            {
                // 上报失败则保留日志,下次启动再尝试
                Console.WriteLine($"上报离线错误日志失败: {ex.Message}");
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 02:44:52