Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ab9adbd
refactor: 更新日志逻辑
ArgoZhang Jan 21, 2026
b2a017a
test: 更新单元测试
ArgoZhang Jan 21, 2026
5a3cd88
doc: 更新示例
ArgoZhang Jan 21, 2026
5a70964
test: 更新单元测试
ArgoZhang Jan 21, 2026
0a3a5a2
feat: 增加 ErrorRender 组件
ArgoZhang Jan 21, 2026
200965b
doc: 文档格式化
ArgoZhang Jan 21, 2026
e9285f3
doc: 文档格式化
ArgoZhang Jan 21, 2026
31179f3
Merge branch 'main' into fix-error
ArgoZhang Jan 21, 2026
6eed865
refactor: 增加异常日志等逻辑
ArgoZhang Jan 21, 2026
8086de2
test: 更新单元测试
ArgoZhang Jan 21, 2026
292810a
Merge branch 'main' into fix-error
ArgoZhang Jan 22, 2026
6aa5263
refactor: 增加 ToastTitle 本地化位置
ArgoZhang Jan 22, 2026
a9b66c4
doc: 更新注释
ArgoZhang Jan 22, 2026
4edac12
feat: 增加日志开关逻辑
ArgoZhang Jan 22, 2026
6d9319a
refactor: 重构异常处理机制
ArgoZhang Jan 22, 2026
2939a4c
test: 更新单元测试
ArgoZhang Jan 22, 2026
00c877a
refactor: 重构代码
ArgoZhang Jan 22, 2026
cdda94d
test: 排除单元测试
ArgoZhang Jan 22, 2026
5a14521
doc: 更新示例
ArgoZhang Jan 22, 2026
aadd3ce
refactor: 重构代码
ArgoZhang Jan 22, 2026
2669c1b
test: 更新单元测试
ArgoZhang Jan 22, 2026
8ae2cc4
test: 支持弹窗显示错误信息
ArgoZhang Jan 22, 2026
cf01d37
test: 增加单元测试
ArgoZhang Jan 22, 2026
4a45288
doc: 更新示例
ArgoZhang Jan 22, 2026
a74fc6d
doc: 增加排序
ArgoZhang Jan 22, 2026
de38fcd
chore: 重构代码
ArgoZhang Jan 22, 2026
9ffadb5
doc: 更新注释
ArgoZhang Jan 22, 2026
d43af10
chore: bump version 10.2.2
ArgoZhang Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/BootstrapBlazor.Server/Components/Pages/Error.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@page "/error"

@page "/error"

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
Expand All @@ -13,4 +12,4 @@
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</p>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private Task OnMaxSelectedCountExceed()

private Task OnTreeItemChecked(List<TreeViewItem<TreeFoo>> items)
{
Logger2.Log($"当前共选中{items.Count}项");
Logger2.Log($"当前共选中 {items.Count} 项");
return Task.CompletedTask;
}

Expand All @@ -201,9 +201,7 @@ private static List<TreeFoo> GetDraggableItems()

new() { Text = "Item C", Id = "3", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item H", Id = "7", ParentId = "3", Icon = "fa-solid fa-font-awesome" },
new() { Text = "Item I", Id = "8", ParentId = "3", Icon = "fa-solid fa-font-awesome" },


new() { Text = "Item I", Id = "8", ParentId = "3", Icon = "fa-solid fa-font-awesome" }
];
return _dragItems;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Logging;
using System.Reflection;

Expand All @@ -20,9 +20,8 @@ class BootstrapBlazorErrorBoundary : ErrorBoundaryBase
[NotNull]
private ILogger<BootstrapBlazorErrorBoundary>? Logger { get; set; }

[Inject]
[NotNull]
private IConfiguration? Configuration { get; set; }
[Inject, NotNull]
private IErrorBoundaryLogger? ErrorBoundaryLogger { get; set; }

[Inject]
[NotNull]
Expand Down Expand Up @@ -61,20 +60,13 @@ class BootstrapBlazorErrorBoundary : ErrorBoundaryBase
[NotNull]
public string? ToastTitle { get; set; }

[CascadingParameter, NotNull]
private IErrorLogger? ErrorLogger { get; set; }

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="exception"></param>
protected override Task OnErrorAsync(Exception exception)
protected override async Task OnErrorAsync(Exception exception)
{
if (EnableILogger && Logger.IsEnabled(LogLevel.Error))
{
Logger.LogError(exception, "BootstrapBlazorErrorBoundary OnErrorAsync log this error occurred at {Page}", NavigationManager.Uri);
}
return Task.CompletedTask;
await ErrorBoundaryLogger.LogErrorAsync(exception);
}

/// <summary>
Expand All @@ -83,135 +75,56 @@ protected override Task OnErrorAsync(Exception exception)
/// <param name="builder"></param>
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// 页面生命周期内异常直接调用这里
// Exceptions in page lifecycle call here directly
var pageException = false;
var ex = CurrentException ?? _exception;
if (ex != null)
if (CurrentException is null)
{
pageException = IsPageException(ex);

// 重置 CurrentException
// Reset CurrentException
ResetException();

// 渲染异常
// Render Exception
var handler = GetLastOrDefaultHandler();
_ = RenderException(ex, handler);
builder.AddContent(0, ChildContent);
}

// 判断是否为组件周期内异常
// Determine if it is a component lifecycle exception
if (!pageException)
else if (ErrorContent is not null)
{
// 渲染正常内容
// Render Normal Content
builder.AddContent(1, ChildContent);
builder.AddContent(1, ErrorContent(CurrentException));
}
}

private static readonly string[] PageMethods = new string[] { "SetParametersAsync", "RunInitAndSetParametersAsync", "OnAfterRenderAsync" };

private static bool IsPageException(Exception ex)
{
var errorMessage = ex.ToString();
return PageMethods.Any(i => errorMessage.Contains(i, StringComparison.OrdinalIgnoreCase));
}

private IHandlerException? GetLastOrDefaultHandler()
{
IHandlerException? handler = null;
if (ErrorLogger is Components.ErrorLogger logger)
else
{
handler = logger.GetLastOrDefaultHandler();
var pageException = IsPageException(CurrentException);
if (pageException)
{
RenderException(builder, CurrentException);
}
else
{
builder.AddContent(0, ChildContent);
}
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated
ResetException();
}
return handler;
}

private PropertyInfo? _currentExceptionPropertyInfo;

private void ResetException()
{
_exception = null;

_currentExceptionPropertyInfo ??= GetType().BaseType!.GetProperty(nameof(CurrentException), BindingFlags.NonPublic | BindingFlags.Instance)!;
_currentExceptionPropertyInfo.SetValue(this, null);
}

private Exception? _exception = null;

private RenderFragment<Exception> ExceptionContent => ex => builder =>
private void RenderException(RenderTreeBuilder builder, Exception ex)
{
if (ErrorContent != null)
if (OnErrorHandleAsync is not null)
{
builder.AddContent(0, ErrorContent(ex));
var renderTask = OnErrorHandleAsync(Logger, ex);
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated
}
Comment thread
ArgoZhang marked this conversation as resolved.
else
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "error-stack");
builder.AddContent(2, GetErrorContentMarkupString(ex));
builder.OpenComponent<ErrorRender>(0);
builder.AddAttribute(1, "Exception", ex);
builder.CloseElement();
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated
}
};
}

private bool? _errorDetails;
private static readonly string[] PageMethods = new string[] { "SetParametersAsync", "RunInitAndSetParametersAsync", "OnAfterRenderAsync" };

private MarkupString GetErrorContentMarkupString(Exception ex)
private static bool IsPageException(Exception ex)
{
_errorDetails ??= Configuration.GetValue("DetailedErrors", false);
return _errorDetails is true
? ex.FormatMarkupString(Configuration.GetEnvironmentInformation())
: new MarkupString(ex.Message);
var errorMessage = ex.ToString();
return PageMethods.Any(i => errorMessage.Contains(i, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
/// <para lang="zh">BootstrapBlazor 组件导致异常渲染方法</para>
/// <para lang="en">BootstrapBlazor Component Exception Render Method</para>
/// </summary>
/// <param name="exception"></param>
/// <param name="handler"></param>
public async Task RenderException(Exception exception, IHandlerException? handler)
{
// 记录日志
// Log Error
await OnErrorAsync(exception);

// 外部调用
// External Call
if (OnErrorHandleAsync != null)
{
await OnErrorHandleAsync(Logger, exception);
}

if (handler != null)
{
// IHandlerException 处理异常逻辑
// IHandlerException Handle Exception Logic
await handler.HandlerExceptionAsync(exception, ExceptionContent);
}
else
{
// 显示异常信息
// Show Exception Info
await ShowErrorToast(exception);
}
}
private PropertyInfo? _currentExceptionPropertyInfo;

private async Task ShowErrorToast(Exception exception)
private void ResetException()
{
if (ShowToast)
{
var option = new ToastOption()
{
Category = ToastCategory.Error,
Title = ToastTitle,
ChildContent = ErrorContent == null
? ExceptionContent(exception)
: ErrorContent(exception)
};
await ToastService.Show(option);
}
_currentExceptionPropertyInfo ??= GetType().BaseType!.GetProperty(nameof(CurrentException), BindingFlags.NonPublic | BindingFlags.Instance)!;
_currentExceptionPropertyInfo.SetValue(this, null);
}
}
24 changes: 17 additions & 7 deletions src/BootstrapBlazor/Components/ErrorLogger/ErrorLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@
[Parameter]
public Func<IErrorLogger, Task>? OnInitializedCallback { get; set; }

[NotNull]
private BootstrapBlazorErrorBoundary? _errorBoundary = default;

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -131,19 +128,32 @@
builder.AddAttribute(4, nameof(BootstrapBlazorErrorBoundary.ErrorContent), ErrorContent);
builder.AddAttribute(5, nameof(BootstrapBlazorErrorBoundary.ChildContent), ChildContent);
builder.AddAttribute(6, nameof(BootstrapBlazorErrorBoundary.EnableILogger), EnableILogger);
builder.AddComponentReferenceCapture(7, obj => _errorBoundary = (BootstrapBlazorErrorBoundary)obj);
builder.CloseComponent();
};

/// <summary>
/// <inheritdoc cref="IErrorLogger.HandlerExceptionAsync(Exception)"/>
/// </summary>
public Task HandlerExceptionAsync(Exception exception) => _errorBoundary.RenderException(exception, GetLastOrDefaultHandler());
public async Task HandlerExceptionAsync(Exception exception)
{
var handler = _cache.LastOrDefault();
if (handler is not null)
{
await handler.HandlerExceptionAsync(exception, ex => builder =>
{
builder.OpenComponent<ErrorRender>(0);
builder.AddAttribute(1, "Exception", ex);
builder.CloseElement();
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated
});
}
if (OnErrorHandleAsync is not null)
{
await OnErrorHandleAsync(exception);

Check failure on line 151 in src/BootstrapBlazor/Components/ErrorLogger/ErrorLogger.cs

View workflow job for this annotation

GitHub Actions / run test

There is no argument given that corresponds to the required parameter 'arg2' of 'Func<ILogger, Exception, Task>'
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated
}
}

private readonly List<IHandlerException> _cache = [];

internal IHandlerException? GetLastOrDefaultHandler() => _cache.LastOrDefault();

/// <summary>
/// <inheritdoc cref="IErrorLogger.Register(IHandlerException)"/>
/// </summary>
Expand Down
62 changes: 62 additions & 0 deletions src/BootstrapBlazor/Components/ErrorLogger/ErrorRender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Configuration;

namespace BootstrapBlazor.Components;

class ErrorRender : IComponent
{
[Inject]
[NotNull]
private IConfiguration? Configuration { get; set; }
Comment thread
ArgoZhang marked this conversation as resolved.

private RenderHandle _renderHandle;

void IComponent.Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
}

private Exception? _ex;

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Task SetParametersAsync(ParameterView parameters)
{
_ex = parameters.GetValueOrDefault<Exception>("Exception");
_renderHandle.Render(BuildRenderTree);
return Task.CompletedTask;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="builder"></param>
private void BuildRenderTree(RenderTreeBuilder builder)
{
if (_ex is not null)
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "error-stack");
builder.AddContent(2, GetErrorContentMarkupString(_ex));
builder.CloseElement();
}
}

private bool? _errorDetails;
private MarkupString GetErrorContentMarkupString(Exception ex)
{
_errorDetails ??= Configuration.GetValue("DetailedErrors", false);
return _errorDetails is true
? ex.FormatMarkupString(Configuration.GetEnvironmentInformation())
: new MarkupString(ex.Message);
}
}
10 changes: 9 additions & 1 deletion src/BootstrapBlazor/Components/Tab/TabItemContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace BootstrapBlazor.Components;

Expand Down Expand Up @@ -79,7 +80,7 @@ private void BuildRenderTree(RenderTreeBuilder builder)
_logger.Register(this);
return Task.CompletedTask;
}));
builder.AddAttribute(7, nameof(ErrorLogger.OnErrorHandleAsync), TabSet.OnErrorHandleAsync);
builder.AddAttribute(7, nameof(ErrorLogger.OnErrorHandleAsync), TabSet.OnErrorHandleAsync ?? HandlerErrorCoreAsync);
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
builder.CloseComponent();
}

Expand All @@ -96,6 +97,13 @@ public void Render()
private bool _detailedErrorsLoaded;
private bool _showDetailedErrors;

private Task HandlerErrorCoreAsync(ILogger logger, Exception ex) => HandlerExceptionAsync(ex, ex => builder =>
{
builder.OpenComponent<ErrorRender>(0);
builder.AddAttribute(1, "Exception", ex);
builder.CloseComponent();
});
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion test/UnitTest/Components/ErrorLoggerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public async Task OnErrorHandleAsync_Ok()
});
});
var button = cut.Find("button");
button.TriggerEvent("onclick", EventArgs.Empty);
await cut.InvokeAsync(() => button.Click());
var result = await tcs.Task;
Assert.True(result);
}
Expand Down
Loading
Loading