Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
923922f
feat(Tab): 增加 ErrorLogger 相关参数
ArgoZhang Jan 10, 2026
7defa04
refactor: 移除开发模式判断
ArgoZhang Jan 10, 2026
4165df3
refactor: 更改回调接口
ArgoZhang Jan 10, 2026
4bf25ff
refactor: 增加内部方法 GetLastOrDefaultHandler
ArgoZhang Jan 10, 2026
5249e5b
refactor: 增加级联参数 IErrorLogger
ArgoZhang Jan 10, 2026
52a5b2e
refactor: 更改渲染逻辑支持原生组件
ArgoZhang Jan 10, 2026
3f75a7c
refactor: 更新代码
ArgoZhang Jan 10, 2026
70e69cd
Merge branch 'main' into feat-tab
ArgoZhang Jan 10, 2026
d6ee142
Merge branch 'main' into feat-tab
ArgoZhang Jan 10, 2026
a425497
refactor: 更改为 LastOrDefault 方法
ArgoZhang Jan 10, 2026
5762b66
refactor: 优化代码
ArgoZhang Jan 10, 2026
ebcb084
refactor: 优化代码
ArgoZhang Jan 10, 2026
9279700
refactor: 增加组件周期异常判断
ArgoZhang Jan 10, 2026
8190290
refactor: 更新代码
ArgoZhang Jan 10, 2026
cc5e331
refactor: 增加组件生命周期判断
ArgoZhang Jan 11, 2026
0c2a5bf
refactor: 增加生命周期报错支持
ArgoZhang Jan 11, 2026
9bb064c
test: 增加单元测试
ArgoZhang Jan 11, 2026
c4b4c05
test: 更新单元测试
ArgoZhang Jan 11, 2026
9fe3b92
refactor: 移除不使用的级联参数
ArgoZhang Jan 11, 2026
479217b
refactor: 优化代码
ArgoZhang Jan 11, 2026
c1b8472
test: 更新单元测试
ArgoZhang Jan 11, 2026
15df5b3
test: 更新单元测试
ArgoZhang Jan 11, 2026
5ce0e77
test: 更新单元测试
ArgoZhang Jan 11, 2026
3f81089
test: 更新单元测试
ArgoZhang Jan 11, 2026
d3767f8
Merge branch 'main' into feat-tab
ArgoZhang Jan 13, 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
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class BootstrapBlazorErrorBoundary : ErrorBoundaryBase
[NotNull]
public string? ToastTitle { get; set; }

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

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -76,29 +79,46 @@ protected override Task OnErrorAsync(Exception exception)
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// 页面生命周期内异常直接调用这里
var pageException = false;
var ex = CurrentException ?? _exception;
if (ex != null)
{
// 处理自定义异常逻辑
if (OnErrorHandleAsync != null)
{
_ = OnErrorHandleAsync(Logger, ex);
return;
}

// 渲染异常内容
builder.AddContent(0, ExceptionContent(ex));
pageException = IsPageException(ex);

// 重置 CurrentException
ResetException();

// 渲染异常
var handler = GetLastOrDefaultHandler();
_ = RenderException(ex, handler);
}
else

// 判断是否为组件周期内异常
if (!pageException)
{
// 渲染正常内容
builder.AddContent(1, ChildContent);
}
}

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)
{
handler = logger.GetLastOrDefaultHandler();
}
return handler;
}

private PropertyInfo? _currentExceptionPropertyInfo;

private void ResetException()
Expand All @@ -119,10 +139,9 @@ private void ResetException()
}
else
{
var index = 0;
builder.OpenElement(index++, "div");
builder.AddAttribute(index++, "class", "error-stack");
builder.AddContent(index++, GetErrorContentMarkupString(ex));
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "error-stack");
builder.AddContent(2, GetErrorContentMarkupString(ex));
builder.CloseElement();
}
};
Expand All @@ -144,34 +163,40 @@ private MarkupString GetErrorContentMarkupString(Exception ex)
/// <param name="handler"></param>
public async Task RenderException(Exception exception, IHandlerException? handler)
{
// 记录日志
await OnErrorAsync(exception);

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

// 记录日志
await OnErrorAsync(exception);

if (handler != null)
{
// 非开发模式下弹窗提示错误信息
await ToastService.Error(ToastTitle, exception.Message);
return;
// IHandlerException 处理异常逻辑
await handler.HandlerExceptionAsync(exception, ExceptionContent);
}
else
{
// 显示异常信息
await ShowErrorToast(exception);
}

// 显示异常信息
await ShowErrorToast(exception);
_exception = exception;
StateHasChanged();
}

private async Task ShowErrorToast(Exception exception)
{
if (ShowToast)
{
await ToastService.Error(ToastTitle, exception.Message);
var option = new ToastOption()
{
Category = ToastCategory.Error,
Title = ToastTitle,
ChildContent = ErrorContent == null
? ExceptionContent(exception)
: ErrorContent(exception)
};
await ToastService.Show(option);
}
}
}
4 changes: 3 additions & 1 deletion src/BootstrapBlazor/Components/ErrorLogger/ErrorLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public Task HandlerExceptionAsync(Exception exception) => _errorBoundary.RenderException(exception, _cache.LastOrDefault());
public Task HandlerExceptionAsync(Exception exception) => _errorBoundary.RenderException(exception, GetLastOrDefaultHandler());

private readonly List<IHandlerException> _cache = [];

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

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/BootstrapBlazor/Components/Layout/Layout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@
RefreshToolbarTooltipText="@RefreshToolbarTooltipText" FullscreenToolbarTooltipText="@FullscreenToolbarTooltipText"
OnToolbarRefreshCallback="OnToolbarRefreshCallback" TabHeader="TabHeader" OnCloseTabItemAsync="OnCloseTabItemAsync"
Body="@Main" NotAuthorized="NotAuthorized!" NotFound="NotFound!" NotFoundTabText="@NotFoundTabText"
EnableErrorLogger="@EnableLogger" ErrorLoggerToastTitle="@ErrorLoggerToastTitle">
EnableErrorLogger="@EnableLogger" EnableErrorLoggerILogger="@EnableErrorLoggerILogger"
ShowErrorLoggerToast="ShowErrorLoggerToast" ErrorLoggerToastTitle="@ErrorLoggerToastTitle"
OnErrorHandleAsync="OnErrorHandleAsync">
</Tab>;

RenderFragment RenderFooter =>
Expand Down
19 changes: 19 additions & 0 deletions src/BootstrapBlazor/Components/Tab/Tab.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Reflection;

Expand Down Expand Up @@ -442,12 +443,30 @@ public partial class Tab
[Parameter]
public bool? EnableErrorLogger { get; set; }

/// <summary>
/// 获得/设置 是否记录异常到 <see cref="ILogger"/> 默认 null 使用 <see cref="BootstrapBlazorOptions.EnableErrorLoggerILogger"/> 设置值
/// </summary>
[Parameter]
public bool? EnableErrorLoggerILogger { get; set; }

/// <summary>
/// 获得/设置 是否显示 Error 提示弹窗 默认 null 使用 <see cref="BootstrapBlazorOptions.ShowErrorLoggerToast"/> 设置值
/// </summary>
[Parameter]
public bool? ShowErrorLoggerToast { get; set; }

/// <summary>
/// 获得/设置 错误日志 <see cref="Toast"/> 弹窗标题 默认 null
/// </summary>
[Parameter]
public string? ErrorLoggerToastTitle { get; set; }

/// <summary>
/// 获得/设置 自定义错误处理回调方法
/// </summary>
[Parameter]
public Func<ILogger, Exception, Task>? OnErrorHandleAsync { get; set; }

[CascadingParameter]
private Layout? Layout { get; set; }

Expand Down
53 changes: 40 additions & 13 deletions src/BootstrapBlazor/Components/Tab/TabItemContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

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

namespace BootstrapBlazor.Components;

Expand All @@ -15,19 +16,24 @@ class TabItemContent : IComponent, IHandlerException, IDisposable
[Parameter, NotNull]
public TabItem? Item { get; set; }

[CascadingParameter]
private Layout? Layout { get; set; }

[CascadingParameter, NotNull]
private Tab? TabSet { get; set; }

[Inject, NotNull]
private DialogService? DialogService { get; set; }

[Inject]
[NotNull]
private ToastService? ToastService { get; set; }

[Inject]
[NotNull]
private IOptionsMonitor<BootstrapBlazorOptions>? Options { get; set; }

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

private IErrorLogger? _logger;

private RenderHandle _renderHandle;
Expand All @@ -52,25 +58,27 @@ private void RenderContent()

private Guid _key = Guid.NewGuid();

private bool EnableErrorLogger => TabSet.EnableErrorLogger ?? Options.CurrentValue.EnableErrorLogger;
private bool EnableErrorLoggerILogger => TabSet.EnableErrorLoggerILogger ?? Options.CurrentValue.EnableErrorLoggerILogger;
private bool ShowErrorLoggerToast => TabSet.ShowErrorLoggerToast ?? Options.CurrentValue.ShowErrorLoggerToast;
private string ToastTitle => TabSet.ErrorLoggerToastTitle ?? "Error";

private void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent<ErrorLogger>(0);
builder.SetKey(_key);
builder.AddAttribute(1, nameof(ErrorLogger.ChildContent), Item.ChildContent);

var enableErrorLogger = TabSet.EnableErrorLogger ?? Options.CurrentValue.EnableErrorLogger;
builder.AddAttribute(2, nameof(ErrorLogger.EnableErrorLogger), enableErrorLogger);

// TabItem 不需要 Toast 提示错误信息
builder.AddAttribute(3, nameof(ErrorLogger.ShowToast), false);
builder.AddAttribute(4, nameof(ErrorLogger.ToastTitle), TabSet.ErrorLoggerToastTitle);
builder.AddAttribute(5, nameof(ErrorLogger.OnInitializedCallback), new Func<IErrorLogger, Task>(logger =>
builder.AddAttribute(2, nameof(ErrorLogger.EnableErrorLogger), EnableErrorLogger);
builder.AddAttribute(3, nameof(ErrorLogger.EnableILogger), EnableErrorLoggerILogger);
builder.AddAttribute(4, nameof(ErrorLogger.ShowToast), ShowErrorLoggerToast);
builder.AddAttribute(5, nameof(ErrorLogger.ToastTitle), ToastTitle);
builder.AddAttribute(6, nameof(ErrorLogger.OnInitializedCallback), new Func<IErrorLogger, Task>(logger =>
{
_logger = logger;
_logger.Register(this);
return Task.CompletedTask;
}));
builder.AddAttribute(6, nameof(ErrorLogger.OnErrorHandleAsync), Layout?.OnErrorHandleAsync);
builder.AddAttribute(7, nameof(ErrorLogger.OnErrorHandleAsync), TabSet.OnErrorHandleAsync);
builder.CloseComponent();
}

Expand All @@ -83,12 +91,31 @@ public void Render()
RenderContent();
}

private bool _detailedErrorsLoaded;
private bool _showDetailedErrors;
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="ex"></param>
/// <param name="errorContent"></param>
public Task HandlerExceptionAsync(Exception ex, RenderFragment<Exception> errorContent) => DialogService.ShowErrorHandlerDialog(errorContent(ex));
public async Task HandlerExceptionAsync(Exception ex, RenderFragment<Exception> errorContent)
{
if (!_detailedErrorsLoaded)
{
_showDetailedErrors = Configuration.GetValue("DetailedErrors", false);
_detailedErrorsLoaded = true;
}

var useDialog = _showDetailedErrors || !ShowErrorLoggerToast;
if (useDialog)
{
await DialogService.ShowErrorHandlerDialog(errorContent(ex));
}
else
{
await ToastService.Error(ToastTitle, ex.Message);
}
}

/// <summary>
/// IDispose 方法用于释放资源
Expand Down
Loading