BlazorのErrorBoundaryを活用してみた!!
BlazorのErrorBundaryって便利ですよね。
もうErrorBoundaryのない世界を想像できません🐈
ちなみに読み方は"エラーバウンダリ"です。
どうも、あっきーです🍊
前回、BlazorのErrorBoundaryを紹介させていただきました。
前回に続いて、もう少し活用例を考えていきたいと思います。
1. エラーの内容に合わせてエラーハンドリングする
前回まではエラーハンドリングをErrorBoundaryで実装して
処理を共通化しました。
次は発生したエラーに合わせて
エラーハンドリングの処理を変えてみたいと思います。
まずはイメージしやすいように実装後の動きをご覧ください!
上記のように発生したエラーに合わせて、
エラー後の動きをErrorBoudary側で実装することができました。
次のように実装しました。
※ あくまで簡易的な実装ですのでその点はご了承ください。
まずはエラーの内容を判別できるようにExceptionを定義します🚨
今回は2種類用意しました。
(もちろん標準のExceptionを使ってもいいと思います。)
FrontException.cs
特に今回は特別なことはしていないですが、
ErrorCodeなど保持させて画面に表示させてもいいですよね!
namespace BlazorApp_Test.Components.Exeptions
{
public class FrontException : Exception
{
public FrontException() { }
public FrontException(string message) : base(message) { }
public FrontException(string message, Exception innerException) : base(message, innerException) { }
}
}
ApiException.cs
namespace BlazorApp_Test.Components.Exeptions
{
public class ApiException : Exception
{
public ApiException() { }
public ApiException(string message) : base(message) { }
public ApiException(string message, Exception innerException) : base(message, innerException) { }
}
}
次にエラー画面を作成です💻
こちらも2種類用意しました。
Error.razor
エラーメッセージの表示とホーム画面へ戻れるようにしています。
@page "/error"
<PageTitle>Error</PageTitle>
<div>ご迷惑をお掛けし申し訳ございません。情シスまでお問い合わせください。</div>
<button @onclick="OnClick">ホームへ戻る</button>
@code {
[Inject]
protected NavigationManager? NavigationManager { get; set; }
private void OnClick()
{
// ホーム画面へ遷移する
NavigationManager?.NavigateTo("/");
}
}
FrontError.razor
Error.razorと同じです。
@page "/front-error"
<PageTitle>FrontError</PageTitle>
<div>フロントのエラーが発生⚠️⚠️⚠️</div>
<button @onclick="OnClick">ホームへ戻る</button>
@code {
[Inject]
protected NavigationManager? NavigationManager { get; set; }
private void OnClick()
{
// ホーム画面へ遷移する
NavigationManager?.NavigateTo("/");
}
}
やっと主役のErrorBoundaryの実装です🫅
MainErrorBoundary.razor
ApiExceptionの時にアラートを表示させるようにしました。
@using BlazorApp_Test.Components.Exeptions
@inherits ErrorBoundary
@if (CurrentException == null)
{
@ChildContent
}
else if(CurrentException is ApiException)
{
// アラートで通知(実装めんどくさいのでjsで対応します)
<script>
alert("通信中にエラーが発生しました。再実行してください。");
</script>
@ChildContent
}
else if (ErrorContent != null)
{
@ErrorContent(CurrentException)
}
else
{
<div class=blazor-error-boundary></div>
}
MainErrorBoundary.razor.cs
Exceptionに合わせて処理を変えています。
using BlazorApp_Test.Components.Exeptions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace BlazorApp_Test.Components.Organisms
{
public partial class MainErrorBoundary : ErrorBoundary
{
[Inject]
protected NavigationManager? NavigationManager { get; set; }
protected override Task OnErrorAsync(Exception exception)
{
// エラーをハンドリングする
HandleError(exception);
return base.OnErrorAsync(exception);
}
protected override void OnParametersSet()
{
Recover();
}
private void HandleError(Exception exception)
{
if (exception is FrontException)
{
NavigationManager?.NavigateTo("front-error");
}
else if (exception is ApiException)
{
//
}
else
{
// 想定外のエラー
NavigationManager?.NavigateTo("error");
}
}
}
}
最後にエラーを発火する画面です。
Counter.razor
@page "/counter"
@using BlazorApp_Test.Components.Exeptions
<PageTitle>Counter</PageTitle>
<h1>Counter🐸</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">カウント3でエラー</button>
<button class="btn btn-primary" @onclick="FrontError">クライアントの謎エラー</button>
<button class="btn btn-primary" @onclick="ApiError">API実行時エラー</button>
@code {
private int currentCount = 0;
[Inject]
protected IJSRuntime JSRuntime { get; set; }
private void IncrementCount()
{
currentCount++;
if (currentCount == 3)
{
// 予期せぬエラーを発生させる
throw new Exception();
}
}
private void FrontError()
{
try
{
Console.WriteLine("何かの処理を実行!!");
throw new Exception();
}
catch
{
throw new FrontException();
}
}
private void ApiError()
{
try
{
Console.WriteLine("APIを実行!!");
throw new Exception();
}
catch
{
throw new ApiException();
}
}
}
今回は3パターンに分けて実装してみました。
エラーの内容に合わせてハンドリングができますね。
2. JSのエラーもハンドリングする
今まではすべてBlazorに閉じたエラーのハンドリングでした。
ブラウザで実行されるアプリだと、
どうしてもJSを使うことが出てきますよね。
JSで起きるエラーもハンドリングしておきましょう!
相互運用中のJSエラーをハンドリングする
まずはエラーを発生させてみます。
Counter.razor
JSのtrrigerJsError関数をBrazorから呼び出しを追加
@page "/counter"
@using BlazorApp_Test.Components.Exeptions
<PageTitle>Counter</PageTitle>
<h1>Counter🐸</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">カウント3でエラー</button>
<button class="btn btn-primary" @onclick="FrontError">クライアントの謎エラー</button>
<button class="btn btn-primary" @onclick="ApiError">API実行時エラー</button>
<button class="btn btn-primary" @onclick="JsError">JSエラー</button>
@code {
private int currentCount = 0;
[Inject]
protected IJSRuntime? JSRuntime { get; set; }
// 一部省略
private async Task JsError()
{
Console.WriteLine("JSを実行!!");
await JSRuntime.InvokeVoidAsync("trrigerJsError");
}
}
common.js
JSのtrrigerJsError関数を実装。
エラーを発火させる🔥🔥🔥
(() => {
window.trrigerJsError = () => {
console.log('trrigerJsError');
throw new Error("JS Error");
};
})();
index.html
common.jsを指定して参照できるようにする
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 省略 -->
</head>
<body>
<div id="app">
<!-- 省略 -->
</div>
<!-- common.jsを指定 -->
<script src="js/common.js?v=0.0.0"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
MainErrorBoundary.cs
JSExceptionの分岐を追加してフロントエラー画面へ遷移させる
using BlazorApp_Test.Components.Exeptions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace BlazorApp_Test.Components.Organisms
{
public partial class MainErrorBoundary : ErrorBoundary
{
[Inject]
protected NavigationManager? NavigationManager { get; set; }
[Inject]
protected IJSRuntime? JSRuntime { get; set; }
protected override Task OnErrorAsync(Exception exception)
{
// エラーをハンドリングする
HandleError(exception);
return base.OnErrorAsync(exception);
}
protected override void OnParametersSet()
{
Recover();
}
private void HandleError(Exception exception)
{
if (exception is FrontException)
{
NavigationManager?.NavigateTo("front-error");
}
else if (exception is ApiException)
{
//
}
else if (exception is JSException)
{
// JSエラー
NavigationManager?.NavigateTo("front-error");
}
else
{
// 想定外のエラー
NavigationManager?.NavigateTo("error");
}
}
}
}
いざ!動確!!🚀
JSExceptionは.NETからJSへの相互運用呼び出し中に発生するエラーです。
JSExceptionを条件分岐に加えることで
JS相互運用中に発生したエラーのハンドリングも可能になります。
相互運用外のJSエラーもハンドリングする
JSExceptionでは相互運用外のJSエラーはハンドリングできません。
確認してみましょう!!
common.jsを少し変更してみます。
setTimeoutで相互運用完了後にエラーを発生させます。
(() => {
window.trrigerJsError = () => {
console.log('trrigerJsError');
// throw new Error("JS Error");
setTimeout(() => { throw new Error("JS Error"); }, 0);
};
})();
いざ!確認!🚀
Consoleを見るとJSエラーが吐かれていますが、
ErrorBoundaryではキャッチできていないため、
ハンドリングができていません。
このままだと、
アプリが壊れた状態で操作がされてしまう可能性があります😢
まず、実装前に完成後の姿をお見せします📺
Consoleを見るとJSエラーが吐かれていて、
かつフロントエラー画面へ遷移しています。
これはErrorBoundaryでエラーハンドリングを行っています。
それでは実装を見て行きましょう👀👀👀
common.jsにJSエラーイベントを監視します🎦
JSエラー発生時のエラー情報を
BrazorのHandleJSErrorメソッドに渡すようにします。
(() => {
// 一部省略
window.errorBoundaryInterop = {
setup: function (dotNetObj) {
window.addEventListener('error', function (event) {
const { message: _msg, error, filename } = event;
const {
name,
message = '',
stack = '',
} = error ?? {
name: '',
message: _msg,
// error情報を取得できない場合はerror発生ファイルを送付
stack: `filename: ${filename}`,
};
dotNetObj.invokeMethodAsync('HandleJSError', name, message, stack);
});
},
};
})();
MainErrorBoundary.razor.csで
errorBoundaryInterop.setupの呼び出しと
HandleJSErrorメソッドを実装します。
using BlazorApp_Test.Components.Exeptions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace BlazorApp_Test.Components.Organisms
{
public partial class MainErrorBoundary : ErrorBoundary
{
[Inject]
protected NavigationManager? NavigationManager { get; set; }
[Inject]
protected IJSRuntime? JSRuntime { get; set; }
protected override Task OnErrorAsync(Exception exception)
{
// 省略
}
protected override void OnParametersSet()
{
// 省略
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("errorBoundaryInterop.setup", DotNetObjectReference.Create(this));
}
await base.OnAfterRenderAsync(firstRender);
}
private void HandleError(Exception exception)
{
// 省略
}
[JSInvokable("HandleJSError")]
public void HandleJSError(string name, string message, string stack)
{
HandleError(new FrontException(message, stack));
}
}
}
これでJSエラーもErrorBoundaryで
エラーハンドリングできるようになりました。
うん、良きですね!
どんどんエラーハンドリング処理を
一か所にまとめられるようになってきました!
最後に
弊社は一緒に働いていただける方を募集中です!
就職/転職活動中や、まだ情報収集中の方、
少しでも興味を持っていただけた方は、以下のアドレスに「note見た!」とご連絡いただけると幸いです💡
プロダクト推進部/採用担当アドレス:pdo_js@persol.co.jp