0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Blazor <ErrorBoundary>をカスタマイズしてGlobal エラーハンドリング(.Net 8)

Last updated at Posted at 2024-12-16

1.この記事の目的

Blazorでは<ErrorBoundary>で効率よくGlobalなエラーハンドリングが実現できます。
また、簡単なカスタマイズでログ出力やエラーページのレイアウト変更など可能なので整理しました。

2.前提条件

  • Visual studio 2022 Version 17.12.3
  • .Net 8
  • UI:Blazor Web App
  • Interactive render mode : Server
  • Interactivity location : Global
    image.png

Interactivity location を Global から Per Pageにすると<ErrorBoundary>でのグローバルエラーハンドリングが複雑になるためここでは考えません。

3.<ErrorBoundary>

3.1 <ErrorBoundary>の実装

構文

<ErrorBoundary>
  <ChildContent>
     {正常時の表示コンテント}
  </ChildContent>
  <ErrorContent>
     {異常キャッチ時の表示コンテント}
  </ErrorContent>
</ErrorBoundary>

MainLayout.razorにて@Body<ErrorBoundary>で囲みます。

<article class="content px-4">
    @Body
</article>

↓ 変更

<article class="content px-4">
    <ErrorBoundary>
        <!-- 正常系 -->
        <ChildContent>
            @Body
        </ChildContent>
        <!-- エラー系 -->
        <ErrorContent>
            <p class="text-danger">システムエラーが発生</p>
            <div class="mt-3">
                <a href="" @onclick="NavigateToHome">Homeへ</a>
            </div>
        </ErrorContent>
    </ErrorBoundary>
</article>

3.2 エラー発生タイミングごとの挙動

InteractiveServerレンダーモードではエラー発生タイミングが大きく2種類存在します。

タイミング 概要
A HTTPリクエストの応答中 GETなどのページ取得時等
B SignalR通信中 ボタンクリックイベントなど初期ページ表示後のイベント等

(A) HTTPリクエストの応答中のエラー

HTTPリクエスト応答中にOnInitializedでエラーを発生させます。

Weather.razor

@page "/weather"

<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>HTTPリクエスト時に強制エラー</p>
@code {
    protected override void OnInitialized()
    {
        throw new Exception("OnInitializedで強制エラー発生");
    }
}

挙動
image.png

エラーがハンドリングされ、<ErrorContent>で用意したコンテントが表示されます。

(B) SignalR通信中のエラー

Counter.razor

@page "/counter"

<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount++;
        if (currentCount > 3)
        {
            throw new Exception("counter IncrementCount");
        }
    }
}

挙動
image.png

同じです。
エラーがハンドリングされ、<ErrorContent>で用意したコンテントが表示されます。

4.デフォルトのログ出力

4.1 コンソール出力

image.png
以下が確認できる。

  • ErrorBoundryがExceptionをキャッチしたこと
  • ThrowしたExceptionのメッセージ「OnInitializedで強制エラー発生」
  • エラー発生時のスタックトレース

4.2 Windowsイベントログ

image.png
同様の内容が確認できる。

5.ログ出力の強化

<ErrorBoundary>を継承し、カスタマイズします
OnErrorAsyncoverrideし、エラー発生時に好みの処理を記述します

/Layout/CustomErrorBoundary.razor

@inherits ErrorBoundary
@inject ILogger<CustomErrorBoundary> Logger

@if (CurrentException is null)
{
    @ChildContent
}
else if (ErrorContent is not null)
{
    @ErrorContent(CurrentException)
}

@code {
    protected override Task OnErrorAsync(Exception ex)
    {
        Logger.LogError(ex, "共通エラーハンドリング");
        return Task.CompletedTask;
    }
}

MainLayout.razor

<ErrorBoundary>
      ・・・
</ErrorBoundary>

  ↓ 書き換え

<CustomErrorBoundary>
      ・・・
</CustomErrorBoundary>

5.1 コンソール出力

image.png
以下が確認できる。

  • ErrorBoundryではなく、CustomErrorBoundryでキャッチしている
  • warnで出力されていたログが、LogErrorで書き込んでいるのでfailに変わっている
  • LogErrorで書き込むときに追加したメッセージ「共通エラーハンドリング」が確認できる

5.2 Windowsイベントログ

image.png

同様の内容が確認できる。

6.エラーページの強化

<ErrorContent>をコンポーネントとして独立させます

MainLayout.razor

<ErrorContent>
    <p class="text-danger">システムエラーが発生</p>
    <div class="mt-3">
        <a href="" @onclick="NavigateToHome">Homeへ</a>
    </div>
</ErrorContent>

  ↓

<ErrorContent>
    <MyError />
</ErrorContent>

/Layout/MyError.razor

<div class="container text-center mt-5">
    <div class="card shadow-lg">
        <div class="card-body">
            <h1 class="text-danger">
                <i class="bi bi-exclamation-triangle-fill"></i> システムエラー
            </h1>
            <p class="text-muted mt-3">
                申し訳ありませんが、問題が発生しました。しばらくしてから再度お試しください。
            </p>
            <div class="mt-4">
                <button class="btn btn-primary btn-lg" @onclick="NavigateToHome">
                    <i class="bi bi-house-door-fill"></i> Homeへ戻る
                </button>
            </div>
        </div>
    </div>
</div>

@code {
    [Inject] private NavigationManager NavigationManager { get; set; } = default!;

    private void NavigateToHome()
    {
        // 完全なページリロードを実行
        NavigationManager.NavigateTo("/", forceLoad: true);
    }
}

挙動
image.png

7.注意事項

7.1 デフォルトの開発環境エラーページ

見慣れたデフォルトのエラーページ(UseDeveloperExceptionPage)ですが、<ErrorBoundary>が優先されるため表示されなくなります。

image.png

7.2 デフォルトのエラーページ

デフォルトのprogram.csにUseExceptionHandlerが記述されてます。

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);

こちらも<ErrorBoundary>が優先されるため、"/Error"へリダイレクトされません。

8.参考

9.全ソース

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?