(30/30)大家一起學Blazor:.NET 6 <ErrorBoundary>

(30/30)大家一起學Blazor:.NET 6 <ErrorBoundary>

昨天說到單元測試,但有些時候可能因為時間關係沒辦法完整測試

最後更新 2021/12/25 下午9:50
StrayaWorker
預計閱讀 5 分鐘
分類
Blazor
專題
一起學Blazor系列
標籤
.NET C# ASP.NET Core Blazor

昨天說到單元測試,但有時候可能因為時間關係沒辦法完整測試,就可能因為某個 Component 出錯導致整個系統崩潰(如下圖),因為Blazor Server 是在 Server 建立一個circuit(流程),一旦有未處理的錯誤 Server 就會將 circuit 終止以避免安全問題。

我們不想每次出錯都終止整個系統,也不想在每個方法都用 try…catch… 包住程式處理所有錯誤,所以要用.NET 6 新推出的 Component <ErrorBoundary>

React 這個前端框架中同樣有 ErrorBoundary 概念,它會捕捉任何 Component 產生的錯誤並展示預設 UI 頁面,當錯誤發生就會將錯誤限縮到該 Component,其他 Component 則維持功能性。

Blazor 也是借用這個概念,不過 Blazor 團隊有說明這並不能捕捉所有可能的例外狀況,而且 <ErrorBoundary> 並不是要處理全域錯誤攔截,那是 ILogger 的任務,<ErrorBoundary>主要目的還是處理渲染或是生命週期方法(OnInitializedAsync、OnParametersSetAsync)產生的錯誤,另外還有許多可能的風險,例如開發者以為 <ErrorBoundary> 可以處理所有錯誤、開發者覺得不需要再針對不同錯誤產生客製化 UI 而導致複數不同錯誤產生時感到困惑、大量同類型 <ErrorBoundary> 同時產生時開發者又剛好針對該錯誤記錄每筆 log 可能導致 log 阻塞…等等,所以不能把這個當成處理錯誤的最後一關。

雖然上面說得有些驚悚,但 <ErrorBoundary> 用來處理簡單的頁面邏輯還是可以的,就來試試看吧!

(註:如果是直接用 Visual Studio 2022 建立專案的人可以省略下面一段的步驟)

因為筆者用的是Visual Studio 2019,.NET 6 只能以Visual Studio 2022 執行,所以先去下載Visual Studio 2022,接著開啟BlazorPractice 方案,將BlazorServerBlazorServerMsTest 兩個專案的 <TargetFramework> 都改成net6.0

然後記得去 wwwroot/css/site.css 加入下列 class,<ErrorBoundary>會產生一個帶有blazor-error-boundary class<div>

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

再去 MainLayout.razor<ErrorBoundary>@Body 包住,最後去 BlogBase.razor.csLoadData() 加入一段程式拋出錯誤。

private async Task LoadData()
{
    // ...
    throw new Exception("這是測試錯誤訊息");
}

開啟網站看看,可以看到這次沒有再看到底下黃色的例外狀況錯誤訊息,而是呈現預設的 UI。

不過不管什麼錯誤都會呈現這樣的資訊,對使用者來說似乎不太友善,我們來用 <ChildContent><ErrorContent> 客製化,它們其實就是RenderFragment,可以放入任何HTML 標籤或是Component

<ErrorBoundary>
  <ChildContent> @Body </ChildContent>
  <ErrorContent>
    <p>很抱歉,目前出現未知錯誤,請聯絡管理員</p>
  </ErrorContent>
</ErrorBoundary>

但這時候如果切換到Roles 或是Users,會發現那塊紅色錯誤訊息依舊在頁面上,這是因為 <ErrorBoundary> 只要偵測到錯誤就會呈現,此時要呼叫方法Recover(),這個方法可以將錯誤數量重設為0,並呼叫 StateHasChanged()去通知各個 Component 狀態已經改變了,如此就會將 Component 重新渲染,千萬記得要使用 @ref 去引用指定的<ErrorBoundary>,否則 Recover() 是不會執行的。

…
<div class="content px-4">
  <ErrorBoundary @ref="errorBoundary">
    <ChildContent> @Body </ChildContent>
    <ErrorContent>
      <p>很抱歉,目前出現未知錯誤,請聯絡管理員</p>
    </ErrorContent>
  </ErrorBoundary>
</div>
…
@code {
    private ErrorBoundary errorBoundary;
    protected override void OnParametersSet()
    {
        errorBoundary?.Recover();
    }
}

另外 <ErrorBoundary> 有一個變數 MaximumErrorCount 預設 100,只要 MaximumErrorCount 超過指定數量系統就會崩潰。

感言

筆者當初報名IT 鐵人賽只是想把寫專案的心得記錄下來,當時很擔心會沒辦法完賽,結果第 17 天真的忘記了,實在很慚愧,竟然犯下這種低級錯誤。後面幾天因為想到已經失敗就有些灌水了,這心態實在不可取,筆者會再將幾篇文章合併,另發新的主題。

雖然沒辦法完賽,但筆者也從記錄心得中學到了一些東西,過去一年多工作就算寫心得也都是片段式紀錄,沒有完整始末,這次鐵人賽為了寫得詳細,很多資料都查了好幾遍,這才是正確的寫心得方式,希望明年的鐵人賽會有更多的進步。

引用:

  1. Unhandled Exceptions in Blazor Server with Error Boundaries
  2. Blazor "Error boundaries" design proposal #30940
  3. Blazor .NET 6 - Error Boundaries - Custom UI for Errors
  4. How To Get .NET 6 in Visual Studio 2019

(註:筆者照這影片的做法還是無法在 Visual Studio 2019 切換 .NET 6,若有人有其他方法還請告知)

Ref:

  1. Download .NET SDKs for Visual Studio

(註:微軟官方下載網站指名 .NET 6 不支援 Visual Studio 2019 SDK,不清楚上面影片的作者是如何辦到的。)

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2021/12/25

(29/30)大家一起學Blazor:Blazor單元測試

開發一個系統最無聊的過程大概就是解決 Bug 了,尤其是那種嘗試對 null 物件取值的錯誤(`Object reference not set to an instance of an object.`),這應該是大部分人剛踏入程式設計領域最常碰到的問題,為了從枯燥的解決 Bug 過程解脫,這篇就來介紹`單元測試`。

繼續閱讀
同分類 / 同標籤 2021/12/25

(28/30)大家一起學Blazor:Policy-based authorization

之前有說到`ASP.NET Core Identity` 使用的是基於`Claim` 的驗證,其實`ASP.NET Core Identity` 有不同類型的授權方式,最簡單的`登入授權`、`角色授權`、`Claim 授權`,但上述幾種都是以一種方式實現:原則授權(`Policy-based authorization`)。

繼續閱讀