由於筆者當初是用 ASP.NET Core API + Blazor Server,所以會以 Blazor Server 示範,日後研究完 Blazor WebAssembly 會再將心得補上。
首先既然 Component 是可以重複利用的,我們在 Index.razor 放上兩個 Counter,啟動專案(如果不想完整偵錯,可以按 Ctrl+F5,就會啟動不偵錯模式,啟動速度比較快,而且每次儲存檔案,Blazor 都會監測到,網頁重新載入就可以載入新程式了),瀏覽器上兩個 Counter 有各自的 Click me 按鈕,分別點擊後可以看到數字分別增加,代表是不同的 Component,那這些數字又定義在哪裡呢?
打開 Counter.razor,最上面是@page 指示詞,這個稍後再說。再來是 HTML 跟一些 C# 程式,最後是@code 區塊,這就是 Blazor 的奇妙之處了,@code 相當於一般網頁 JS 做的事情,諸如定義變數、實作方法、發送 request 到後端或是 API,不過 Blazor 用 C# 編寫,這裡定義了一個私有變數 currentCount,還有一個方法 IncrementCount(),呼叫這方法的是 Click me 按鈕,每一次點擊按鈕都會使 currentCount+1,而呈現結果就在 p 元素內。


currentCount 定義的方式跟頁面呈現就是一種模型綁定(model binding),意思是資料跟頁面有綁定關係,.NET Framework 的 View 的@model 或是@Viewbag,Angular 的[(ngModel)]也是同理,都是要做到資料流到頁面後,對頁面操作可以影響資料的行為。
我們來定義另一個變數 myClass,給這變數一些 bootstrap 的 class,再把變數放在 button 的 class 裡面,記住在 HTML 裡面用到 C# 的程式必須以@開頭,不然 Blazor 不知道要編譯。重新載入頁面可以看到按鈕的樣式變了,Blazor 幫我們把 myClass 的值 text-primary bg-warning 放進 button 的 class。

接著我們看 FetchData.razor,這裡看到了@using BlazorServer.Data,我們待會可以把這個 using 放進_import.razor,那麼@inject WeatherForecastService ForecastService 又是什麼呢?我們先看@code 區塊,看到這裡定義了 WeatherForecast 陣列型別的變數 forecasts,且用非同步方法 OnInitializedAsync 呼叫了 ForecastService.GetForecastAsync(DateTime.Now),將結果回傳 forecasts,眼尖的人應該發現了最上面的 ForecastService 跟@code 區塊的 ForecastService 一模一樣。

我們點一下 GetForecastAsync() 方法並按下 F12,可以看到這個方法回傳的就是 5 個隨機產生的天氣資料陣列,HTML 裡有判斷 forecasts 是否為 null,不是的話就產生一個 table,裡面用 foreach 將 forecasts 的日期、攝氏、華氏及天氣狀態一一呈現出來。

前面說過 Blazor 只有一個網頁,其他內容都是一個個 Component 組成的,每次觸發事件,Server 或是 WebAssembly 都會將對應 Component 呈現在瀏覽器上,但 Blazor 怎麼知道現在要呈現哪個 Component 呢?
原因就是@page 指示詞,這個指示詞相當於傳統的路由,可以看到 Index.razor 的@page 為"/",表示這是首頁,Counter.razor 及 FetchData.razor 也有相應的@page 指示詞。一個頁面可以有多個@page 指示詞,不過開頭一定要有斜線且用雙引號包起來,筆者曾想過建立 enum 集中管理不同 Component 的@page,可惜目前 Blazor 不支援這種做法。另外若兩個 Component 用了相同的@page,編譯階段就會出現錯誤提示,所以也不用擔心若有重複路由 Blazor 會怎麼處理。

那麼左邊選單的 Home, Counter, Fetch data 頁面又是在哪裡定義的呢?打開 MainLayout.razor,可以看到 NavMenu 元素,再打開 NavMenu.razor,可以看到三個 NavLink Component,這些 Component 會被 Server 翻譯為瀏覽器認識的 a 元素,因此就算我們打開 Dev tool,也只會看到 a 元素。



回到 MainLayout.razor,可以看到@Body 指示詞,這就是其他 Component 會放置的地方,可以說是種 placeholder,再看 App.razor 裡面有 Found 及 NotFound 兩個 Component,從字面看就知道,前者是當輸入的網址找到匹配的 Component 則會進入這裡,後者則是找不到匹配的 Component,可以看到兩者都用了 MainLayout。另外若有不同頁面要套用不同 Layout,也可以自己定義。


說到這裡,我們複習一下 Blazor Server 是怎麼走的,可以看到跟 Angular 類似都是一層一層往下,管理較為方便。Blazor WebAssembly 跟 Blazor Server 的 index.html 跟_Layout.cshtml 大致相等,以及缺少了 appsettings.json 檔案,通常會將程式跟資料庫連線需要的連線字串放在這個檔案,可證 Blazor WebAssembly 確實只是被動接收資料,而無法主動跟資料庫連線,筆者曾試過在這裡引用 EF Core,也是無法讓 Blazor WebAssembly 接觸資料庫,在.NET Framework 的世界是用 XML 格式的 web.config,在.NET Core 則改用 JSON 格式的 appsettings.json。
引用: ASP NET Core blazor project structure
註:本文程式碼透過 .NET 6 + Visual Studio 2022 重構,可點擊原文連結與重構後程式碼比較學習,謝謝閱讀,支持原作者