不得不說,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-ui
的 style.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 解法終究只是暫時性的而已。
留言