コンテンツにスキップ

集約エラーハンドラーの実装⚓︎

本章では、 SSR ベースの Blazor Server アプリケーションにおける集約エラーハンドラーの構成と実装手順を解説します。 本章の手順を実施することで、 本番環境で Blazor ランタイムおよび .NET ランタイム内でシステム例外が発生した場合、エラーページに遷移する機能が実装されます。

システム例外の処理方針については、全体処理方式 - 例外処理方針 を参照してください。

集約エラーハンドリングの全体像⚓︎

本章では、次の内容を実施します。

  • Blazor ランタイム内で発生した未処理例外を表示するコンポーネント( Error.razor )を実装します。
  • ErrorBoundaryError.razor を囲むようにレイアウトします。
  • .NET ランタイム( Blazor 起動前)で発生した例外を表示する Razor Pages ベースのエラーページ ( ServerError.cshtml )を実装します。
  • エントリーポイント( Program.cs )で、上記のエラーハンドリングを有効にするための設定をします。

プロジェクトの作成 で作成した Fluent Blazor Web アプリのテンプレートから変更すべき点は、以下の通りです。

├ {ApplicationName}.Web
├ Components
│ ├ Layout
│ │  └ MainLayout.razor ............ 変更します。
│ ├ Pages
│ │ ├ Error.razor ................... 変更します。
│ │ └ Error.razor.css ............... 追加します。
│ └ _imports.razor .................. 変更します。
├ Pages
│   └ ServerError.cshtml ............ 追加します。
└ Program.cs ........................ 変更します。

レイアウトの変更⚓︎

このセクションでは、アプリケーションの共通レイアウト(MainLayout.razor)を変更し、 Blazor ランタイム内の未処理例外を集約して扱う方法を説明します。

MainLayout.razor を次のように修正し、エラー境界を導入します。 このことにより、子コンポーネントで発生した未処理例外をまとめてキャッチし、エラーページの表示やエラー後の回復処理を一元化します。

  • MainLayout.razor のビューに ErrorBoundary コンポーネントを追加します。
MainLayout.razorの変更点(抜粋)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    <FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
        <NavMenu />
        <FluentBodyContent Class="body-content">
            <div class="content">
                <ErrorBoundary @ref="errorBoundary">
                    <ChildContent>
                        @Body
                    </ChildContent>
                    <ErrorContent>
                        <Error Exception="@context" />
                    </ErrorContent>
                </ErrorBoundary>
            </div>
        </FluentBodyContent>
    </FluentStack>
    <FluentFooter>
  • @code ブロックを追加します。 OnParametersSet() のタイミングで Recover() 処理をすることで、エラー状態から通常の状態へ復旧するように仕込んでいます。
MainLayout.razor に追加する @code ブロック
1
2
3
4
5
6
7
8
@code {
    private ErrorBoundary? errorBoundary;

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

ErrorBoundary コンポーネントの詳細については、ASP.NET Core Blazor アプリのエラーを処理する - エラー境界 を参照してください。

エラーコンポーネントの設定⚓︎

このセクションでは、 Blazor ランタイム内の未処理例外をユーザーに表示するためのコンポーネント Error.razor の実装方針を説明します。 開発環境でのデバッグ効率を高めるため、 Exception のスタックトレースの情報を表示するように実装します。

Error.razor の実装例を下記に示します。 開発者が扱いやすいように、トップページへのリンク機能とスタックトレースの表示機能を実装しています。 開発環境かつ例外の情報がある場合にのみ、スタックトレースを表示します。

サンプルアプリケーションの Error.razor
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@page "/Error"
@attribute [AllowAnonymous]

@using System.Diagnostics
@using System.Diagnostics.CodeAnalysis

<PageTitle>Dressca-CMS - 内部サエラ</PageTitle>

<div>
    <h3>内部サエラ</h3>
    <FluentStack Orientation="Orientation.Vertical" VerticalGap="30">
        <div>
            申し訳ありません。処理中に予期しないエラが発生しました。<br />
            時間をおいて再度アクセスしてください。問題が解決しない場合は、管理者へお問い合わせください。
        </div>
        <FluentAnchor Href="/" Appearance="Appearance.Hypertext">トップペジに戻る</FluentAnchor>
        @if (this.ShowStackTrace)
        {
            <FluentCard>
                <h4>スタックトレ</h4>
                <pre>@this.Exception.ToString()</pre>
            </FluentCard>
        }
    </FluentStack>
</div>

@code {
    [Parameter]
    public Exception? Exception { get; set; }

    // ASP.NET Core では、 IWebHostEnvironment は常に登録されており、
    // null になることはないため、 null 非許容にはしていません。
    [Inject]
    private IWebHostEnvironment Environment { get; set; } = default!;

    [MemberNotNullWhen(true, nameof(this.Exception))]
    private bool ShowStackTrace => this.Environment.IsDevelopment() && this.Exception is not null;
}

エラーページの実装⚓︎

.NET ランタイム側で発生した例外を扱うためのエラーページを追加します。 Blazor の起動前にエラーをキャッチする必要があるので、 Razor Components ではなく、 Razor Pages( .cshtml )として実装します。 Razor Pages のファイルは、 Razor Components を格納する Pages フォルダーとは異なるプロジェクトルート直下の Pages フォルダー内に必ず作成してください。

注意:Razor Pages と Razor Components

Razor Pages はページ指向のアーキテクチャーを採用している一方で、Razor Components はコンポーネント指向のアーキテクチャーを採用しています。そのため、 Razor Pages では、 1つの URL に対して 1つのページ( .cshtml )が対応することが想定されています。よって、 Razor Pages を用いる場合、 URL に紐づくファイル名およびパスは Pages フォルダ配下のフォルダー階層によって決定されるので、異なる場所に配置しないよう注意してください。Razor Pages についての詳細な解説は、Razor ASP.NET Core のページのアーキテクチャと概念 を参照してください。

├ {ApplicationName}.Web
├ Components
│ ├ Pages
│ │ │ Error.razor
│ │ └ ServerError.cshtml --- NG
├ Pages
└   └ ServerError.cshtml --- OK

下記にエラーページの実装例を示します。

エラーページの実装例
サンプルアプリケーションの ServerError.cshtml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@page "/ServerError"
@{
    Layout = null;
}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>内部サーバーエラー</title>
    <meta name="robots" content="noindex" />
    <style>
        body {
            margin: 0;
            font-family: "Segoe UI", system-ui, sans-serif;
            background: #f5f5f5;
            display: flex;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
        }

        .container {
            width: 100%;
            max-width: 600px;
            box-sizing: border-box;
            padding: 32px 40px;
            background: #fff;
            border-radius: 12px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.08);
        }

        h1 {
            margin: 0 0 16px;
            font-size: 32px;
            font-weight: 600;
        }

        p {
            margin: 0 0 24px;
            line-height: 1.6;
        }

        a { color: #004578; text-decoration: none; }
        a:hover { text-decoration: underline; }

        .footer {
            font-size: 18px;
            color: #666;
            margin-top: 16px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>サーバーで問題が発生しています。</h1>
        <p>
            申し訳ありません。処理中に予期しないエラーが発生しました。<br />
            時間をおいて再度アクセスしてください。問題が解決しない場合は、管理者へお問い合わせください。
        </p>
        <a href="/">トップへ戻る</a>
        <div class="footer">&copy; @(DateTime.UtcNow.Year) Dressca CMS</div>
    </div>
</body>
</html>

エントリーポイントの設定⚓︎

Razor Pages として追加した ServerError.cshtml を動作させるため、 エントリーポイントで Razor Pages 関連の設定をします。 Program.cs を下記のように変更します。

Program.cs の変更点(抜粋)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using Microsoft.AspNetCore.Hosting.StaticWebAssets;

// 中略

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
builder.Services.AddFluentUIComponents();
builder.Services.AddRazorPages(); // Razor Pages の機能一式を DI コンテナに登録します。

if (builder.Environment.IsDevelopment()) // 開発環境用の設定です。
{
    StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration); // 静的アセットの読み込みを構成します。
}

// 中略

if (!app.Environment.IsDevelopment()) // 本番環境用の設定です。
{
    app.UseExceptionHandler("/ServerError", createScopeForErrors: true); // ServerError.cshtml のパスを設定します。

    app.UseHsts();
}

// 中略

app.MapStaticAssets();

app.MapRazorPages(); // 追加した Razor Pages ( ServerError.cshtml )をルーティングに登録します。

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

動作確認⚓︎

Blazor ランタイムの例外の確認⚓︎

Home.razor に下記のようにわざと例外を発生させる @code ブロックを実装し、アプリケーションを起動します。

例外を発生させる Home.razor
1
2
3
4
5
6
7
8
@code {

    protected override void OnParametersSet()
    {

        throw new Exception("エラーページの動作確認");
    }
}

アプリケーションの起動と同時に、 Error.razor のページが表示され、内部サーバーエラーを示すメッセージと、例外のスタックトレースが表示されることを確認してください。 確認ができたら、確認用に追加したコードは削除してください。

.NET ランタイムの例外の確認⚓︎

NavMenu.razor に下記のようにわざと例外を発生させる @code ブロックを実装し、アプリケーションを起動します。

例外を発生させる NavMenu.razor
1
2
3
4
5
6
7
8
9
@code {

    protected override void OnParametersSet()
    {
        throw new Exception("エラーページの動作確認");
    }

    private bool expanded = true;
}

アプリケーションの起動と同時に、「 An unhandled exception occurred while processing the request. 」ともに詳細なエラー情報を示す画面に遷移することを確認して下さい。 確認ができたら、確認用に追加したコードは削除してください。

また、本番環境用のエラーページ ServerError.cshtml には、アドレスバーに直接 /ServerError を打ち込むことで遷移可能です。 こちらのページはエラー発生時にユーザーが閲覧することを想定しているので、詳細なエラー情報を表示しません。