有没有办法全局捕获 Blazor 单页面应用程序中所有未处理的错误?

问题描述 投票:0回答:10

我希望能够在构建 Blazor 单页面应用程序的一个地方捕获所有未处理的异常。 就像在 WPF 应用程序中使用“Current.DispatcherUnhandledException”一样。

这个问题专门关于客户端(WebAssembly)异常处理。 我使用的是 Blazor 版本 3.0.0-preview8.19405.7

我一直在寻找解决方案,但似乎不存在。在 Microsoft 文档 (https://learn.microsoft.com/en-us/aspnet/core/blazor/handle-errors?view=aspnetcore-3.0) 上,列出了可能发生错误的位置,并进行了演练如何处理它们中的每一个。 它相信一定有一种更防弹的方法来捕获所有。

error-handling unhandled-exception blazor
10个回答
30
投票

在 .NET 6 中,有一个名为 ErrorBoundary 的组件。

简单的例子:

<ErrorBoundary>
   @Body
</ErrorBoundary>

高级示例:

 <ErrorBoundary>
    <ChildContent>
          @Body
    </ChildContent>
    <ErrorContent Context="ex">
          @{ OnError(@ex); } @*calls custom handler*@
          <p>@ex.Message</p> @*prints exeption on page*@
    </ErrorContent>
 </ErrorBoundary>

对于全局异常处理,我认为这是一个选项: 创建

CustomErrorBoundary
(继承
ErrorBoundary
)并覆盖
OnErrorAsync(Exception exception)

这里

CustomErrorBoundary
的样本。

有用的链接


13
投票

这适用于 v3.2+

using Microsoft.Extensions.Logging;
using System;

namespace UnhandledExceptions.Client
{
    public interface IUnhandledExceptionSender
    {
        event EventHandler<Exception> UnhandledExceptionThrown;
    }

    public class UnhandledExceptionSender : ILogger, IUnhandledExceptionSender
    {

        public event EventHandler<Exception> UnhandledExceptionThrown;

        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
            Exception exception, Func<TState, Exception, string> formatter)
        {
            if (exception != null)
            {
                UnhandledExceptionThrown?.Invoke(this, exception);
            }
        }
    }

    public class UnhandledExceptionProvider : ILoggerProvider
    {
        UnhandledExceptionSender _unhandledExceptionSender;

 
        public UnhandledExceptionProvider(UnhandledExceptionSender unhandledExceptionSender)
        {
            _unhandledExceptionSender = unhandledExceptionSender;
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new UnhandledExceptionLogger(categoryName, _unhandledExceptionSender);
        }

        public void Dispose()
        {            
        }

        public class UnhandledExceptionLogger : ILogger
        {
            private readonly string _categoryName;
            private readonly UnhandledExceptionSender _unhandeledExceptionSender;

            public UnhandledExceptionLogger(string categoryName, UnhandledExceptionSender unhandledExceptionSender)
            {
                _unhandeledExceptionSender = unhandledExceptionSender;
                _categoryName = categoryName;
            }

            public bool IsEnabled(LogLevel logLevel)
            {
                return true;
            }

            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
                // Unhandled exceptions will call this method
                // Blazor already logs unhandled exceptions to the browser console
                // but, one could pass the exception to the server to log, this is easily done with serilog
                Serilog.Log.Fatal(exception, exception.Message);                             
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return new NoopDisposable();
            }

            private class NoopDisposable : IDisposable
            {
                public void Dispose()
                {  
                }
            }
        }
    }
}

将此添加到 Program.cs

var unhandledExceptionSender = new UnhandledExceptionSender();
var unhandledExceptionProvider = new UnhandledExceptionProvider(unhandledExceptionSender);
builder.Logging.AddProvider(unhandledExceptionProvider);
builder.Services.AddSingleton<IUnhandledExceptionSender>(unhandledExceptionSender);

这里是实现此解决方案的示例项目。


9
投票

目前没有一个中心位置来捕获和处理客户端异常。

这是 Steve Sanderson 对此的引用

总的来说,每个组件都必须处理自己的错误。如果 你想要的,你可以制作你自己的 ErrorHandlingComponentBase 来 继承并在所有生命周期方法周围放置一个 try/catch, 并有自己的逻辑来显示“哦,亲爱的,对不起,我死了”用户界面 如果出现任何问题,该组件。但这不是它的特色 今天的框架。

我希望这种情况将来会改变,我相信支持应该回到框架中。


7
投票

对于 .NET 5 Blazor 服务器端,这篇文章 Create Your Own Logging Provider to Log to Text Files in .NET Core 对我有用。对于我的情况,我对此进行了调整以捕获未处理的异常以写入 Azure 存储表。

public class ExceptionLoggerOptions
{
    public virtual bool Enabled { get; set; }
}

[ProviderAlias("ExceptionLogger")]
public class ExceptionLoggerProvider : ILoggerProvider
{
    public readonly ExceptionLoggerOptions Options;

    public ExceptionLoggerProvider(IOptions<ExceptionLoggerOptions> _options)
    {
        Options = _options.Value;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new ExceptionLogger(this);
    }

    public void Dispose()
    {
    }
}

public class ExceptionLogger : ILogger
{
    protected readonly ExceptionLoggerProvider _exceptionLoggerProvider;

    public ExceptionLogger([NotNull] ExceptionLoggerProvider exceptionLoggerProvider)
    {
        _exceptionLoggerProvider = exceptionLoggerProvider;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel == LogLevel.Error;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (false == _exceptionLoggerProvider.Options.Enabled) return;

        if (null == exception) return;

        if (false == IsEnabled(logLevel)) return;

        var record = $"{exception.Message}"; // string.Format("{0} {1} {2}",  logLevel.ToString(), formatter(state, exception), exception?.StackTrace);

        // Record exception into Azure Table
    }
}

public static class ExceptionLoggerExtensions
{
    public static ILoggingBuilder AddExceptionLogger(this ILoggingBuilder builder, Action<ExceptionLoggerOptions> configure)
    {
        builder.Services.AddSingleton<ILoggerProvider, ExceptionLoggerProvider>();
        builder.Services.Configure(configure);
        return builder;
    }
}

    public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStaticWebAssets().UseStartup<Startup>();
    }).ConfigureLogging((hostBuilderContext, logging) =>
    {
        logging.AddExceptionLogger(options => { options.Enabled = true; });
    });

2
投票

要访问异常,您可以使用内置的 ErrorBoundary 组件 并使用 Context 属性访问 RenderFragment

<ErrorBoundary> 
    <ChildContent>
        @Body   
    </ChildContent>
    <ErrorContent Context="ex">
        <h1 style="color: red;">Oops... error occured: @ex.Message </h1>
    </ErrorContent>
</ErrorBoundary>

2
投票

这将捕获ALL错误。

App.razor

<ErrorBoundary>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</ErrorBoundary>

如果您想自定义消息:

<ErrorBoundary>
    <ChildContent>
        ... App
    </ChildContent>
    <ErrorContent Context="errorException">

        <div class="blazor-error-boundary">
            Boom!
        </div>

    </ErrorContent>
</ErrorBoundary>

2
投票

在上面的示例中使用 CustomErrorBoundary 和 mudblazor。我制作了一个自定义错误边界组件,在弹出窗口中显示错误。

以防其他人想要这样做。

自定义错误边界.razor

@inherits ErrorBoundary
@inject ISnackbar Snackbar
@if (CurrentException is null)
{
    @ChildContent
}
else if (ErrorContent is not null)
{
    @ErrorContent(CurrentException)
}
else
{
    @ChildContent

        @foreach (var exception in receivedExceptions)
        {
            Snackbar.Add(@exception.Message, Severity.Error);
        }

    Recover();
}

@code {
    List<Exception> receivedExceptions = new();

    protected override Task OnErrorAsync(Exception exception)
    {
        receivedExceptions.Add(exception);
        return base.OnErrorAsync(exception);
    }

    public new void Recover()
    {
        receivedExceptions.Clear();
        base.Recover();
    }
}

MainLayout.razor

@inherits LayoutComponentBase
@inject ISnackbar Snackbar

<MudThemeProvider IsDarkMode="true"/>
<MudDialogProvider />
<MudSnackbarProvider />

<MudLayout>
    <MudAppBar>
        <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
    </MudAppBar>
    <MudDrawer @bind-Open="@_drawerOpen">
        <NavMenu/>
    </MudDrawer>
    <MudMainContent>
        <CustomErrorBoundary>
            @Body
        </CustomErrorBoundary>
    </MudMainContent>
</MudLayout>

@code {
    bool _drawerOpen = true;

    private void DrawerToggle()
    {
        _drawerOpen = !_drawerOpen;
    }
}

1
投票

在当前 Blazor WebAssembly 版本中,所有未处理的异常都被捕获在内部类中并写入

Console.Error
。目前还没有办法以不同的方式捕获它们,但 Rémi Bourgarel 展示了一种能够记录它们和/或采取自定义操作的解决方案。请参阅Remi 的博客

简单的记录器将它们路由到 ILogger:

public class UnhandledExceptionLogger : TextWriter
{
    private readonly TextWriter _consoleErrorLogger;
    private readonly ILogger _logger;

    public override Encoding Encoding => Encoding.UTF8;

    public UnhandledExceptionLogger(ILogger logger)
    {
        _logger = logger;
        _consoleErrorLogger = Console.Error;
        Console.SetError(this);
    }

    public override void WriteLine(string value)
    {
        _logger.LogCritical(value);
        // Must also route thru original logger to trigger error window.
        _consoleErrorLogger.WriteLine(value);
    }
}

现在在 Program.cs 中添加

builder.Services.AddLogging...
并添加:

builder.Services.AddSingleton<UnhandledExceptionLogger>();
...
// Change end of Main() from await builder.Build().RunAsync(); to:
var host = builder.Build();
// Make sure UnhandledExceptionLogger is created at startup:
host.Services.GetService<UnhandledExceptionLogger>();
await host.RunAsync();

0
投票

我发现捕获 Blazor WebAssembly 的所有错误(包括未实现 try catch 的 Async void )的唯一方法是 注册到 AppDomain.CurrentDomain.UnhandledException 事件

在 MainLayout.razor 中

@code (

 protected override async void OnInitialized()
    {
        base.OnInitialized();
         AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
             {
                 //Call your class that handles error
             };
}
}

0
投票

MainLayout.razor

<ErrorBoundary @ref="errorBoundary">
    <ChildContent>
        @Body
    </ChildContent>
    <ErrorContent Context="ex">
        @Body
        @{
            if (ex.Message.Contains("status code"))
                Console.WriteLine(ex.Message);
            else Snackbar.Add(@ex.Message, Severity.Error);
        }
    </ErrorContent>
</ErrorBoundary>

MainLayout.razor.cs

ErrorBoundary errorBoundary;

protected override void OnParametersSet()
{
    errorBoundary?.Recover();
}

index.html

中注释 blazor-error-ui div
    <div id="blazor-error-ui" dir="rtl">
        an error occurred!
        <a href="" class="reload">رفرش</a>
        <a class="dismiss">🗙</a>
    </div>
© www.soinside.com 2019 - 2024. All rights reserved.