Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
@namespace BootstrapBlazor.Components

<CascadingValue Value="this" IsFixed="true">
<ErrorLogger EnableErrorLogger="_enableErrorLoggerValue" ShowToast="_showToast" ToastTitle="@ToastTitle" OnErrorHandleAsync="OnErrorHandleAsync!">
<ErrorLogger EnableErrorLogger="_enableErrorLoggerValue" ShowToast="_showToast" ToastTitle="@ToastTitle"
OnErrorHandleAsync="OnErrorHandleAsync">
@ChildContent

<Dialog></Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,27 @@ protected override async Task OnErrorAsync(Exception exception)
/// <param name="builder"></param>
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
#if DEBUG
// DEBUG 模式下显示异常堆栈信息到 UI 页面方便开发人员调试
if (OnErrorHandleAsync == null)
var ex = CurrentException ?? _exception;
if (ex != null)
{
var ex = CurrentException ?? _exception;
if (ex != null)
_exception = null;

// 处理自定义异常逻辑
if(OnErrorHandleAsync != null)
{
_exception = null;
builder.AddContent(0, ExceptionContent(ex));
// 页面生命周期内异常直接调用这里
_ = OnErrorHandleAsync(Logger, ex);
return;
}

// 渲染异常内容
builder.AddContent(0, ExceptionContent(ex));
}
else
{
// 渲染正常内容
builder.AddContent(1, ChildContent);
}
#endif
builder.AddContent(1, ChildContent);
}

private Exception? _exception = null;
Expand Down
22 changes: 21 additions & 1 deletion src/BootstrapBlazor/Components/ErrorLogger/ErrorLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public class ErrorLogger : ComponentBase, IErrorLogger
[Parameter]
public RenderFragment<Exception>? ErrorContent { get; set; }

/// <summary>
/// Gets or sets the callback function to be invoked during initialization.
/// </summary>
[Parameter]
public Func<ErrorLogger, Task>? OnInitializedCallback { get; set; }

[NotNull]
private BootstrapBlazorErrorBoundary? _errorBoundary = default;

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

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

if (OnInitializedCallback is not null)
{
await OnInitializedCallback(this);
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -97,7 +117,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
};

/// <summary>
/// 由接口调用
/// 由实现 <see cref="BootstrapComponentBase"/> 组件实现类调用
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
Expand Down
2 changes: 0 additions & 2 deletions src/BootstrapBlazor/Components/Filters/FilterContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using System.Runtime.CompilerServices;

namespace BootstrapBlazor.Components;

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion src/BootstrapBlazor/Components/Layout/Layout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@
}
else
{
@HandlerMain()
<ErrorLogger EnableErrorLogger="@_enableErrorLoggerValue" ShowToast="@_showToast" ToastTitle="@ErrorLoggerToastTitle"
OnErrorHandleAsync="OnErrorHandleAsync" OnInitializedCallback="OnErrorLoggerInitialized">
@HandlerMain()
</ErrorLogger>
}
</main>;

Expand Down
43 changes: 43 additions & 0 deletions src/BootstrapBlazor/Components/Layout/Layout.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System.Reflection;

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

/// <summary>
/// 获得/设置 是否开启全局异常捕获 默认 null 读取配置文件 EnableErrorLogger 值
/// </summary>
[Parameter]
public bool? EnableErrorLogger { 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; }

/// <summary>
/// 获得 登录授权信息
/// </summary>
[CascadingParameter]
private Task<AuthenticationState>? AuthenticationStateTask { get; set; }

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

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

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

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

private bool _enableErrorLoggerValue => EnableErrorLogger ?? Options.CurrentValue.EnableErrorLogger;

private bool _showToast => ShowErrorLoggerToast ?? Options.CurrentValue.ShowErrorLoggerToast;

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -625,6 +658,15 @@ private async Task ToggleSidebar()
await TriggerCollapseChanged();
}

private ErrorLogger? _errorLogger;

private Task OnErrorLoggerInitialized(ErrorLogger logger)
{
_errorLogger = logger;
_errorLogger.Register(this);
return Task.CompletedTask;
}

/// <summary>
/// 上次渲染错误内容
/// </summary>
Expand Down Expand Up @@ -678,6 +720,7 @@ protected override async ValueTask DisposeAsync(bool disposing)

if (disposing)
{
_errorLogger?.UnRegister(this);
ErrorLogger?.UnRegister(this);
if (SubscribedLocationChangedEvent)
{
Expand Down
25 changes: 0 additions & 25 deletions test/UnitTest/Components/ErrorHandlerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,6 @@ await cut.InvokeAsync(() => dialog.Show(new DialogOption()
await cut.InvokeAsync(() => btn.Click());
}

[Fact]
public async Task ShowToast_Ok()
{
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
pb.AddChildContent<ErrorComponent>();
});
var errorButton = cut.Find(".btn-error");
await cut.InvokeAsync(() => errorButton.Click());
cut.Contains("<div class=\"toast-body\">test error logger</div>");

// 关闭 Toast
var toast = cut.FindComponent<Toast>().Instance;
await cut.InvokeAsync(() => toast.Close());
cut.DoesNotContain("<div class=\"toast-body\">test error logger</div>");

cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.ShowToast, false);
});
errorButton = cut.Find(".btn-error");
await cut.InvokeAsync(() => errorButton.Click());
cut.DoesNotContain("<div class=\"toast-body\">test error logger</div>");
}

private class MockDialogTest : ComponentBase
{
[Inject]
Expand Down
114 changes: 113 additions & 1 deletion test/UnitTest/Components/LayoutTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,14 @@ public async Task OnUpdateAsync_Ok()
}

[Fact]
public void HandlerException_Ok()
public void IHandlerException_Ok()
{
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
pb.Add(a => a.EnableErrorLogger, true);
pb.AddChildContent<Layout>(pb =>
{
// 按钮触发异常
pb.Add(a => a.Main, new RenderFragment(builder =>
{
builder.OpenComponent<Button>(0);
Expand All @@ -552,9 +553,108 @@ public void HandlerException_Ok()
var button = cut.Find("button");
cut.InvokeAsync(() => button.Click());
cut.Contains("<div class=\"error-stack\">");
cut.Contains("class=\"layout\"");
Context.DisposeComponents();
}

[Fact]
public void ErrorLogger_LifeCycle()
{
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
pb.Add(a => a.EnableErrorLogger, true);
pb.AddChildContent<Layout>(pb =>
{
pb.Add(a => a.UseTabSet, false);
pb.Add(a => a.EnableErrorLogger, true);
pb.Add(a => a.ShowErrorLoggerToast, false);
pb.Add(a => a.ErrorLoggerToastTitle, "Title");
// 按钮触发异常
pb.Add(a => a.Main, new RenderFragment(builder =>
{
builder.OpenComponent<MockPage>(0);
builder.CloseComponent();
}));
});
});
cut.Contains("<div class=\"error-stack\">");
cut.Contains("class=\"layout\"");
}

[Fact]
public void ErrorLogger_OnErrorHandleAsync_Page()
{
// 页面生命周期内报错调用自定义处理方法
Exception? ex1 = null;
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
pb.Add(a => a.EnableErrorLogger, true);
pb.AddChildContent<Layout>(pb =>
{
pb.Add(a => a.UseTabSet, false);
pb.Add(a => a.OnErrorHandleAsync, (logger, ex) =>
{
ex1 = ex;
return Task.CompletedTask;
});
// 按钮触发异常
pb.Add(a => a.Main, new RenderFragment(builder =>
{
builder.OpenComponent<MockPage>(0);
builder.CloseComponent();
}));
});
});
Assert.NotNull(ex1);
}

[Fact]
public void ErrorLogger_OnErrorHandleAsync_Button()
{
// 页面生命周期内报错调用自定义处理方法
Exception? ex1 = null;
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
pb.Add(a => a.EnableErrorLogger, true);
pb.AddChildContent<Layout>(pb =>
{
pb.Add(a => a.OnErrorHandleAsync, (logger, ex) =>
{
ex1 = ex;
return Task.CompletedTask;
});
// 按钮触发异常
pb.Add(a => a.Main, new RenderFragment(builder =>
{
builder.OpenComponent<Button>(0);
builder.AddAttribute(1, nameof(Button.OnClick), EventCallback.Factory.Create<MouseEventArgs>(this, e =>
{
var a = 1;
var b = 0;
var c = a / b;
return Task.CompletedTask;
}));
builder.CloseComponent();
}));
});
});
var button = cut.Find("button");
cut.InvokeAsync(() => button.Click());
Assert.NotNull(ex1);

// 移除自定义逻辑使用内部异常处理逻辑
var layout = cut.FindComponent<Layout>();
layout.SetParametersAndRender(pb =>
{
pb.Add(a => a.OnErrorHandleAsync, null);
});
button = cut.Find("button");

ex1 = null;
cut.InvokeAsync(() => button.Click());
Assert.Null(ex1);
}

[Fact]
public void CollapseBarTemplate_Ok()
{
Expand Down Expand Up @@ -600,3 +700,15 @@ public void Authorized_Ok()
Context.DisposeComponents();
}
}

class MockPage : ComponentBase
{
protected override void OnInitialized()
{
var a = 1;
var b = 0;

// 触发生命周期内异常
var c = a / b;
}
}