.NET 8 での Blazor で ErrorBoundary を使ったエラーハンドリングの注意点
以前にも以下のような似たような記事を書いたことがあります。
この記事を書いた時は .NET 8 も出て日が浅かったこともあり ErrorBoundary
コンポーネントを使ったエラーハンドリングの上手い方法が思いつかなかったため、気合で頑張るみたいな方法を紹介しました。要は例外の出る可能性のあるメソッドで全部 try
catch
しようねという感じです。
もうちょっといい方法
リリースされて半年以上もたつとドキュメントも色々整備・更新されてきていて、レンダリングモードのページではグローバルで SSR/Interactive を切り替えるような方法も紹介されています。
以下のドキュメントの「グローバル対話型アプリの静的 SSR ページ」のセクションです。
こうするとレイアウトファイルから一貫したレンダリングモードを使いつつ、静的 SSR や InteractiveServer/WASM/Auto の切り替えが出来るようになります。つまりレイアウトファイルに ErrorBoundary
を配置しても、ちゃんとエラーが補足されるようになります。
やってみよう
Blazor Web App のプロジェクトで Interactive render mode を Auto にして Interactivity location は Global にしてプロジェクトを作成します。
App.razor
ファイルを以下のようにしてページに設定されているレンダリングモードを Routes
コンポーネントと HeadOutlet
コンポーネントに設定するようにします。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="BlazorApp3.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet @rendermode="PageRenderMode" />
</head>
<body>
<Routes @rendermode="PageRenderMode" />
<script src="_framework/blazor.web.js"></script>
</body>
</html>
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
private IComponentRenderMode? PageRenderMode =>
HttpContext.GetEndpoint()?.Metadata.GetMetadata<RenderModeAttribute>()?.Mode;
}
そしてサーバーサイドのプロジェクトに Components/Layout/StaticSsrLayout.razor
を作成して以下のようにします。
CascadingParameter
で HttpContext
を取得して、それが null
の場合は NavigationManager.Refresh
を呼び出してリロードするようにしています。
これは CascadingParameter
で渡される HttpContext
は InteractiveServer
などのインタラクティブなレンダリングモードの場合は null
になるため、その際にページをリフレッシュすると、結果として現在のページのレンダリングモードに合わせて画面全体が再レンダリングされるといった挙動になります。
@using BlazorApp3.Client.Layout
@inherits LayoutComponentBase
@layout MainLayout
@inject NavigationManager NavigationManager
@if (HttpContext is null)
{
<p>Loading...</p>
}
else
{
@Body
}
@code {
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
protected override void OnParametersSet()
{
if (HttpContext is null)
{
NavigationManager.Refresh(forceReload: true);
}
}
}
そして、Static SSR を適用する予定のページをサーバーサイドのプロジェクトに置いて、このレイアウトを適用するようにします。プロジェクトテンプレートで作成されるサンプルページの Home.razor
と Weather.razor
が今回の対象になります。
Home.razor
のコードは以下のようになります。Weather.razor
も同じようにサーバーサイドのプロジェクトに移動してレイアウトを適用しています。
@page "/"
@using BlazorApp3.Components.Layout
@layout StaticSsrLayout
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
そして MainLayout.razor
に以下のように ErrorBoundary
を配置します。
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
<ErrorBoundary @ref="_errorBoundary">
@Body
</ErrorBoundary>
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@code {
private ErrorBoundary? _errorBoundary;
protected override void OnParametersSet()
{
_errorBoundary?.Recover();
}
}
これで Home.razor
や Counter.razor
や Weather.razor
で例外が発生しても、ErrorBoundary
が補足してエラー画面を表示するようになります。
試しに Counter.razor
や Weather.razor
でエラーを適当に発生させるようなコードを仕込んだ感じ以下のようにちゃんとレイアウト部分で設定している ErrorBoundary
が補足してエラー画面を表示してくれます。
制限事項
ちゃんと動いていそうですが制限事項があります。
この方式を使った時にはアプリケーション内の全ページで一貫したレンダリングモードを設定する必要があります。
例えば InteractiveServer
のページから InteractiveWebAssembly
に遷移をするとエラーになります。これはレイアウト部分が InteractiveServer
でレンダリングされている所に InteractiveWebAssembly
のページがレンダリングされるためです。
ここら辺、うまいやり方があれば教えてください。
まとめ
ということで Static SSR と Interactive render mode の中から 1 種類の 2 つが混在するアプリで ErrorBoundary
を使ってエラーハンドリングをする方法を紹介しました。
これなら、全メソッドに try
catch
するよりも楽にグローバルでエラーハンドリングをすることが出来るので、個人的にはこの方法を使うかなと思います。
InteractiveServer
と InteractiveWebAssembly
間の画面遷移が出来れば完璧だったんですが…。
Discussion