Skip to content

Commit b80e875

Browse files
authored
feat(Layout): integrated ErrorLogger handler global exception (#6119)
* refactor: 代码格式化 * feat: Layout 内置 ErrorLogger * Revert "feat: Layout 内置 ErrorLogger" This reverts commit 945c952. * doc: 更新文档 * refactor: 更新逻辑 * Reapply "feat: Layout 内置 ErrorLogger" This reverts commit 041f6c2. * test: 增加单元测试 * refactor: 增加页面生命周期报错时自定义异常处理逻辑 * test: 更新单元测试 * refactor: 增加单元测试 * test: 移除单元测试 * doc: 移除命名空间
1 parent c06050c commit b80e875

File tree

8 files changed

+200
-40
lines changed

8 files changed

+200
-40
lines changed

src/BootstrapBlazor/Components/BaseComponents/BootstrapBlazorRoot.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
@namespace BootstrapBlazor.Components
33

44
<CascadingValue Value="this" IsFixed="true">
5-
<ErrorLogger EnableErrorLogger="_enableErrorLoggerValue" ShowToast="_showToast" ToastTitle="@ToastTitle" OnErrorHandleAsync="OnErrorHandleAsync!">
5+
<ErrorLogger EnableErrorLogger="_enableErrorLoggerValue" ShowToast="_showToast" ToastTitle="@ToastTitle"
6+
OnErrorHandleAsync="OnErrorHandleAsync">
67
@ChildContent
78

89
<Dialog></Dialog>

src/BootstrapBlazor/Components/ErrorLogger/BootstrapBlazorErrorBoundary.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,27 @@ protected override async Task OnErrorAsync(Exception exception)
6868
/// <param name="builder"></param>
6969
protected override void BuildRenderTree(RenderTreeBuilder builder)
7070
{
71-
#if DEBUG
72-
// DEBUG 模式下显示异常堆栈信息到 UI 页面方便开发人员调试
73-
if (OnErrorHandleAsync == null)
71+
var ex = CurrentException ?? _exception;
72+
if (ex != null)
7473
{
75-
var ex = CurrentException ?? _exception;
76-
if (ex != null)
74+
_exception = null;
75+
76+
// 处理自定义异常逻辑
77+
if(OnErrorHandleAsync != null)
7778
{
78-
_exception = null;
79-
builder.AddContent(0, ExceptionContent(ex));
79+
// 页面生命周期内异常直接调用这里
80+
_ = OnErrorHandleAsync(Logger, ex);
81+
return;
8082
}
83+
84+
// 渲染异常内容
85+
builder.AddContent(0, ExceptionContent(ex));
86+
}
87+
else
88+
{
89+
// 渲染正常内容
90+
builder.AddContent(1, ChildContent);
8191
}
82-
#endif
83-
builder.AddContent(1, ChildContent);
8492
}
8593

8694
private Exception? _exception = null;

src/BootstrapBlazor/Components/ErrorLogger/ErrorLogger.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public class ErrorLogger : ComponentBase, IErrorLogger
5656
[Parameter]
5757
public RenderFragment<Exception>? ErrorContent { get; set; }
5858

59+
/// <summary>
60+
/// Gets or sets the callback function to be invoked during initialization.
61+
/// </summary>
62+
[Parameter]
63+
public Func<ErrorLogger, Task>? OnInitializedCallback { get; set; }
64+
5965
[NotNull]
6066
private BootstrapBlazorErrorBoundary? _errorBoundary = default;
6167

@@ -69,6 +75,20 @@ protected override void OnInitialized()
6975
ToastTitle ??= Localizer[nameof(ToastTitle)];
7076
}
7177

78+
/// <summary>
79+
/// <inheritdoc/>
80+
/// </summary>
81+
/// <returns></returns>
82+
protected override async Task OnInitializedAsync()
83+
{
84+
await base.OnInitializedAsync();
85+
86+
if (OnInitializedCallback is not null)
87+
{
88+
await OnInitializedCallback(this);
89+
}
90+
}
91+
7292
/// <summary>
7393
/// <inheritdoc/>
7494
/// </summary>
@@ -97,7 +117,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
97117
};
98118

99119
/// <summary>
100-
/// 由接口调用
120+
/// 由实现 <see cref="BootstrapComponentBase"/> 组件实现类调用
101121
/// </summary>
102122
/// <param name="exception"></param>
103123
/// <returns></returns>

src/BootstrapBlazor/Components/Filters/FilterContext.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
55

6-
using System.Runtime.CompilerServices;
7-
86
namespace BootstrapBlazor.Components;
97

108
/// <summary>

src/BootstrapBlazor/Components/Layout/Layout.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@
126126
}
127127
else
128128
{
129-
@HandlerMain()
129+
<ErrorLogger EnableErrorLogger="@_enableErrorLoggerValue" ShowToast="@_showToast" ToastTitle="@ErrorLoggerToastTitle"
130+
OnErrorHandleAsync="OnErrorHandleAsync" OnInitializedCallback="OnErrorLoggerInitialized">
131+
@HandlerMain()
132+
</ErrorLogger>
130133
}
131134
</main>;
132135

src/BootstrapBlazor/Components/Layout/Layout.razor.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.AspNetCore.Components.Authorization;
77
using Microsoft.AspNetCore.Components.Routing;
88
using Microsoft.Extensions.Localization;
9+
using Microsoft.Extensions.Logging;
910
using System.Reflection;
1011

1112
namespace BootstrapBlazor.Components;
@@ -452,12 +453,40 @@ public partial class Layout : IHandlerException, ITabHeader
452453
[Parameter]
453454
public object? Resource { get; set; }
454455

456+
/// <summary>
457+
/// 获得/设置 是否开启全局异常捕获 默认 null 读取配置文件 EnableErrorLogger 值
458+
/// </summary>
459+
[Parameter]
460+
public bool? EnableErrorLogger { get; set; }
461+
462+
/// <summary>
463+
/// 获得/设置 是否显示 Error 提示弹窗 默认 null 使用 <see cref="BootstrapBlazorOptions.ShowErrorLoggerToast"/> 设置值
464+
/// </summary>
465+
[Parameter]
466+
public bool? ShowErrorLoggerToast { get; set; }
467+
468+
/// <summary>
469+
/// 获得/设置 错误日志 <see cref="Toast"/> 弹窗标题 默认 null
470+
/// </summary>
471+
[Parameter]
472+
public string? ErrorLoggerToastTitle { get; set; }
473+
474+
/// <summary>
475+
/// 获得/设置 自定义错误处理回调方法
476+
/// </summary>
477+
[Parameter]
478+
public Func<ILogger, Exception, Task>? OnErrorHandleAsync { get; set; }
479+
455480
/// <summary>
456481
/// 获得 登录授权信息
457482
/// </summary>
458483
[CascadingParameter]
459484
private Task<AuthenticationState>? AuthenticationStateTask { get; set; }
460485

486+
[Inject]
487+
[NotNull]
488+
private IOptionsMonitor<BootstrapBlazorOptions>? Options { get; set; }
489+
461490
[Inject, NotNull]
462491
private IServiceProvider? ServiceProvider { get; set; }
463492

@@ -470,6 +499,10 @@ public partial class Layout : IHandlerException, ITabHeader
470499

471500
private ITabHeader? TabHeader => ShowTabInHeader ? this : null;
472501

502+
private bool _enableErrorLoggerValue => EnableErrorLogger ?? Options.CurrentValue.EnableErrorLogger;
503+
504+
private bool _showToast => ShowErrorLoggerToast ?? Options.CurrentValue.ShowErrorLoggerToast;
505+
473506
/// <summary>
474507
/// <inheritdoc/>
475508
/// </summary>
@@ -625,6 +658,15 @@ private async Task ToggleSidebar()
625658
await TriggerCollapseChanged();
626659
}
627660

661+
private ErrorLogger? _errorLogger;
662+
663+
private Task OnErrorLoggerInitialized(ErrorLogger logger)
664+
{
665+
_errorLogger = logger;
666+
_errorLogger.Register(this);
667+
return Task.CompletedTask;
668+
}
669+
628670
/// <summary>
629671
/// 上次渲染错误内容
630672
/// </summary>
@@ -678,6 +720,7 @@ protected override async ValueTask DisposeAsync(bool disposing)
678720

679721
if (disposing)
680722
{
723+
_errorLogger?.UnRegister(this);
681724
ErrorLogger?.UnRegister(this);
682725
if (SubscribedLocationChangedEvent)
683726
{

test/UnitTest/Components/ErrorHandlerTest.cs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,6 @@ await cut.InvokeAsync(() => dialog.Show(new DialogOption()
3232
await cut.InvokeAsync(() => btn.Click());
3333
}
3434

35-
[Fact]
36-
public async Task ShowToast_Ok()
37-
{
38-
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
39-
{
40-
pb.AddChildContent<ErrorComponent>();
41-
});
42-
var errorButton = cut.Find(".btn-error");
43-
await cut.InvokeAsync(() => errorButton.Click());
44-
cut.Contains("<div class=\"toast-body\">test error logger</div>");
45-
46-
// 关闭 Toast
47-
var toast = cut.FindComponent<Toast>().Instance;
48-
await cut.InvokeAsync(() => toast.Close());
49-
cut.DoesNotContain("<div class=\"toast-body\">test error logger</div>");
50-
51-
cut.SetParametersAndRender(pb =>
52-
{
53-
pb.Add(a => a.ShowToast, false);
54-
});
55-
errorButton = cut.Find(".btn-error");
56-
await cut.InvokeAsync(() => errorButton.Click());
57-
cut.DoesNotContain("<div class=\"toast-body\">test error logger</div>");
58-
}
59-
6035
private class MockDialogTest : ComponentBase
6136
{
6237
[Inject]

test/UnitTest/Components/LayoutTest.cs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,13 +528,14 @@ public async Task OnUpdateAsync_Ok()
528528
}
529529

530530
[Fact]
531-
public void HandlerException_Ok()
531+
public void IHandlerException_Ok()
532532
{
533533
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
534534
{
535535
pb.Add(a => a.EnableErrorLogger, true);
536536
pb.AddChildContent<Layout>(pb =>
537537
{
538+
// 按钮触发异常
538539
pb.Add(a => a.Main, new RenderFragment(builder =>
539540
{
540541
builder.OpenComponent<Button>(0);
@@ -552,9 +553,108 @@ public void HandlerException_Ok()
552553
var button = cut.Find("button");
553554
cut.InvokeAsync(() => button.Click());
554555
cut.Contains("<div class=\"error-stack\">");
556+
cut.Contains("class=\"layout\"");
555557
Context.DisposeComponents();
556558
}
557559

560+
[Fact]
561+
public void ErrorLogger_LifeCycle()
562+
{
563+
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
564+
{
565+
pb.Add(a => a.EnableErrorLogger, true);
566+
pb.AddChildContent<Layout>(pb =>
567+
{
568+
pb.Add(a => a.UseTabSet, false);
569+
pb.Add(a => a.EnableErrorLogger, true);
570+
pb.Add(a => a.ShowErrorLoggerToast, false);
571+
pb.Add(a => a.ErrorLoggerToastTitle, "Title");
572+
// 按钮触发异常
573+
pb.Add(a => a.Main, new RenderFragment(builder =>
574+
{
575+
builder.OpenComponent<MockPage>(0);
576+
builder.CloseComponent();
577+
}));
578+
});
579+
});
580+
cut.Contains("<div class=\"error-stack\">");
581+
cut.Contains("class=\"layout\"");
582+
}
583+
584+
[Fact]
585+
public void ErrorLogger_OnErrorHandleAsync_Page()
586+
{
587+
// 页面生命周期内报错调用自定义处理方法
588+
Exception? ex1 = null;
589+
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
590+
{
591+
pb.Add(a => a.EnableErrorLogger, true);
592+
pb.AddChildContent<Layout>(pb =>
593+
{
594+
pb.Add(a => a.UseTabSet, false);
595+
pb.Add(a => a.OnErrorHandleAsync, (logger, ex) =>
596+
{
597+
ex1 = ex;
598+
return Task.CompletedTask;
599+
});
600+
// 按钮触发异常
601+
pb.Add(a => a.Main, new RenderFragment(builder =>
602+
{
603+
builder.OpenComponent<MockPage>(0);
604+
builder.CloseComponent();
605+
}));
606+
});
607+
});
608+
Assert.NotNull(ex1);
609+
}
610+
611+
[Fact]
612+
public void ErrorLogger_OnErrorHandleAsync_Button()
613+
{
614+
// 页面生命周期内报错调用自定义处理方法
615+
Exception? ex1 = null;
616+
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
617+
{
618+
pb.Add(a => a.EnableErrorLogger, true);
619+
pb.AddChildContent<Layout>(pb =>
620+
{
621+
pb.Add(a => a.OnErrorHandleAsync, (logger, ex) =>
622+
{
623+
ex1 = ex;
624+
return Task.CompletedTask;
625+
});
626+
// 按钮触发异常
627+
pb.Add(a => a.Main, new RenderFragment(builder =>
628+
{
629+
builder.OpenComponent<Button>(0);
630+
builder.AddAttribute(1, nameof(Button.OnClick), EventCallback.Factory.Create<MouseEventArgs>(this, e =>
631+
{
632+
var a = 1;
633+
var b = 0;
634+
var c = a / b;
635+
return Task.CompletedTask;
636+
}));
637+
builder.CloseComponent();
638+
}));
639+
});
640+
});
641+
var button = cut.Find("button");
642+
cut.InvokeAsync(() => button.Click());
643+
Assert.NotNull(ex1);
644+
645+
// 移除自定义逻辑使用内部异常处理逻辑
646+
var layout = cut.FindComponent<Layout>();
647+
layout.SetParametersAndRender(pb =>
648+
{
649+
pb.Add(a => a.OnErrorHandleAsync, null);
650+
});
651+
button = cut.Find("button");
652+
653+
ex1 = null;
654+
cut.InvokeAsync(() => button.Click());
655+
Assert.Null(ex1);
656+
}
657+
558658
[Fact]
559659
public void CollapseBarTemplate_Ok()
560660
{
@@ -600,3 +700,15 @@ public void Authorized_Ok()
600700
Context.DisposeComponents();
601701
}
602702
}
703+
704+
class MockPage : ComponentBase
705+
{
706+
protected override void OnInitialized()
707+
{
708+
var a = 1;
709+
var b = 0;
710+
711+
// 触发生命周期内异常
712+
var c = a / b;
713+
}
714+
}

0 commit comments

Comments
 (0)