不得不說,Blazor 雖然是一個很酷的新框架,但是因為它真的還太年輕,很多 API 要不是不足就是開得很爛,剛開始用的時候肯定免不了花上一些功夫解決一些底層的需求(幸好在那之後就可以用得很開心了),而其中一個很爛的 API 就是 Blazor Server 在遇到伺服器斷線或著當迴路(circuit)出現沒被接到的例外時的處理機制。

我們先來看看原本的設計是長怎樣。在專案中的 _Host.cshtml 這個頁面入口檔案之中,Blazor 會期望我們放上兩個元件,然後分別用固定的 id 來命名:

<div id="blazor-error-ui">...</div>

<div id="components-reconnect-modal">...</div>

接著,Blazor 請我們去設定這兩個元件的初始外觀為隱藏,然後 Blazor 會在遇到狀況的時候去顯示這些元件。當迴路出現例外時,它會把 #blazor-error-uistyle.display 設為 block;而如果遇到與伺服器連線中斷的情況,則會把 #components-reconnect-modal 加上三種不同的 class(例如 components-reconnect-show 等等)來對應不同的狀況,然後我們就可以利用 CSS 去針對這三種 class 去定義其子元件的顯示行為。

哇~真是太好了耶,可以自訂其顯示行為耶~

修但幾勒,就只有這樣?我就只能顯示或隱藏一個元件,但除此之外我連一個 JavaScript 的 callback 都執行不了?我真的是服了 Blazor 團隊竟然訂得出這麼無能的錯誤處理 API。如果我想要在例外丟出的時候發送 API 紀錄錯誤呢?或者如果我想要改成用 Bootstrap modal 呈現錯誤訊息呢?在這種設計之下都辦不到。

沒關係,有辦法的!既然知道 Blazor 內部的機制是如上述,我們就有辦法攔截到 Blazor 做的那些動作,然後串接我們自己的 callback。首先,一樣我們必須在網頁上添加前述的兩個指定 id 的元件,不過完全讓它們的內容為空就好;它們的存在就只是要讓 Blazor 覺得我們好像很聽話,有照著它的 API 在玩。

<div id="blazor-error-ui"></div>
<div id="components-reconnect-modal"></div>

但是在網頁的最後面,我們馬上就要來對這兩個元件動手腳、埋下機關來等 Blazor 自投羅網。針對錯誤處理的攔截是這樣的:

let beu = document.getElemenetById('blazor-error-ui');
Object.defineProperty(beu, "style", {
    value: {
        set display(v) {
            // 發生例外時我們自己要做的事情
        }
    },
    writable: false,
    configurable: false,
    enumerable: false
});

換句話說,我們把這個元件的整個 style 都換成了一個我們自創的物件,其唯一的功能就是,一旦 display 屬性被更改,就會執行我們自訂的程式碼。這邊之所以要把整個 style 換掉而不是在源有的 style 物件上修改 display,是因為我發現後者這種作法偏偏在 Safari 上頭沒有任何作用,不過上述的作法是通吃的。

至於連線中斷,則是這樣去攔截:

let crm = document.getElementById('components-reconnect-modal');
crm.classList.add = function(c) {
    if(c == "components-reconnect-show") {
        // 顯示自訂的「嘗試重新連線中……」訊息
    } else {
        // 重新連線失敗的時候要做的事情
    }
}

這回我們比較單純地只是把 add 方法換掉就好了,因為 Blazor 內部的程式碼呼叫的就是這個東西。

不過說真的啦,還是希望 Blazor 以後可以改成比較不要那麼無能的 API;這種 reflection 解法終究只是暫時性的而已。

分享此頁至:
最後修改日期: 2020/09/03

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。