Blazor .NET note

Blazor 是微軟 .NET 家族的一員,它最神奇的地方在於,讓你可以只用 C# 語言,就能同時打造網站的前端(使用者看到的介面)和後端(伺服器邏輯)。

核心特色:

  • 單一語言通吃: 用你最熟悉的 C# 搞定一切,前端、後端無縫接軌,共用模型與邏輯,大幅減少重複工作。
  • 組件化開發: 像玩樂高積木一樣,將網頁拆解成一個個可重複使用的「元件 (Component)」。這種開發模式與 React、Vue 等主流前端框架師出同門,能讓你的專案結構清晰、乾淨且易於維護。
  • 跨平台執行: 不論是在伺服器上運算 (Blazor Server),還是在使用者的瀏覽器裡執行 (Blazor WebAssembly),Blazor 都能辦到。甚至可以透過 .NET MAUI Blazor,將你的網頁元件打包成桌面和行動應用程式!

1.安裝 Visual Studio Code 和 .NET SDK

2.Visual Studio Code 新增 C# 擴充功能

  • 在搜尋欄中輸入“C#”,安裝

3.建立一個新的 Blazor 項目

  • 在終端機中輸入指令: dotnet new blazorwasm -o MyBlazorApp
    • 在名為 MyBlazorApp 的資料夾中建立一個新的 Blazor WebAssembly 專案 。

4.熟悉結構

  • Pages : 包含 .razor 文件,它們是應用程式的主要元件。
  • wwwroot:儲存靜態文件,如圖片、CSS 和 JavaScript。
  • Program.cs:設定服務和中間件以啟動應用程式。

Blazor Server (伺服器端渲染)

  • 運作方式: 所有程式邏輯都在伺服器上執行。瀏覽器初次載入時只是一個輕量的「空殼」,之後使用者與畫面的所有互動,都會透過一個名為 SignalR 的即時通訊管道(通常是 WebSocket)傳送到伺服器。伺服器計算完畢後,只將畫面「有變動的部分」差異傳回給瀏覽器進行更新。
  • 優點:
    • 初始載入快: 因為瀏覽器幾乎不用下載什麼東西。
    • 高安全性: 所有商業邏輯和資料庫連線都安全地保留在伺服器上,不會暴露給客戶端。
    • 完美支援 SEO: 頁面由伺服器完整渲染,對搜尋引擎爬蟲非常友善。
    • 適合低效能裝置: 對使用者的電腦配備要求較低。
  • 缺點:
    • 需要持續連線: 如果網路不穩定或中斷,應用程式就無法操作。
    • 互動延遲 (Latency): 每次點擊都需要一次網路來回,在高延遲的網路環境下,使用者可能會感覺到操作有輕微的延遲。
    • 伺服器負擔較重: 每個連線的使用者都會在伺服器上佔用資源。

適合情境: 需要即時更新資料的儀表板(如股票看板)、注重資料安全的金融或醫療系統、企業內部管理系統(如 ERP、CRM)等。

Blazor WebAssembly (WASM,客戶端渲染)

  • 運作方式: WebAssembly (WASM) 是一種新型的網頁二進位格式,能讓瀏覽器以接近原生的速度執行 C# 程式碼。第一次開啟網站時,會將整個應用程式(包含 .NET 執行環境)打包下載到使用者的瀏覽器中,之後所有的運算和操作都在瀏覽器本地完成,就像一個安裝在瀏覽器裡的桌面應用。
  • 優點:
    • 極致的互動體驗: 一旦下載完成,操作反應就像桌面應用程式一樣迅速,完全沒有網路延遲。
    • 支援離線運作: 應用程式可以被製作成漸進式網路應用 (PWA),即使沒有網路,網站依然可以使用。
    • 降低伺服器負擔: 大部分工作都交給了使用者的電腦,伺服器只負責提供靜態檔案和 API。
    • 善用客戶端資源: 可以執行複雜的運算任務,如遊戲或圖片處理,而不會增加伺服器成本。
  • 缺點:
    • 初始載入慢: 第一次需要下載較大的檔案,等待時間較長(可透過 AOT 編譯和檔案裁剪技術進行優化)。
    • 對裝置要求較高: 需要使用者有比較好的電腦配備來順暢執行。

適合情境: 高度互動的應用程式(如線上圖片編輯器、CAD 工具)、需要離線功能的工具(如任務管理 App)、PWA (漸進式網路應用程式)。

Blazor Auto (自動渲染模式 – .NET 8 新功能)

  • 運作方式: 它結合了 Server 和 WASM 的優點。使用者第一次載入頁面時,它會採用 Server 模式,讓畫面飛快地出現。
    • 同時,它會在背景悄悄地下載 WASM 所需的檔案。
    • 下載完成後,它會無縫地將模式切換到 WASM,讓後續的操作享受極致的互動體驗。
  • 優點: 兩全其美!既有飛快的初始載入速度,又有流暢的後續操作。
  • 適合情境: 大部分面向公眾、同時需要良好初次體驗和高度互動性的新專案。
  1. 標準元件 (Standard Components): 內建的常用元素,如 <InputText>、<InputNumber> 等,開箱即用。
  2. Razor 元件: 你可以完全客製化的元件,透過 HTML 和 C# 混編,打造獨一無二的功能與外觀。
  3. 第三方元件: 由社群或廠商提供的現成元件庫,是加速開發的利器。知名的有 MudBlazor(免費且功能強大)、Radzen Blazor ComponentsTelerik UI for Blazor 等,提供如圖表、複雜表格、對話框等進階功能。

可以把元件的生命週期想像成一間餐廳的日常運作。

OnInitialized:元件初始化時呼叫一次。此方法適合用來設定元件的初始狀態或載入靜態資料。

  • 就像餐廳「準備開店」。

OnInitializedAsync:如果你需要從外部伺服器載入資料(例如 API 來的菜單),這就會花點時間,所以要用非同步的 OnInitializedAsync,來避免程式卡住。

  • 就像餐廳「等食材來準備開店」。

OnParametersSet / SetParametersAsync:當外部傳入的參數 ([Parameter]) 發生變化時觸發。你可以根據客人點的不同餐點(新參數),來更新元件的狀態。SetParametersAsync 是更進階的版本,讓你可以手動控制參數的設定流程。

  • 就像【客人點餐或改單】。

ShouldRender:【服務生判斷】。這個方法讓你決定在接收到更新後,「是否需要」重新渲染畫面。在某些效能敏感的場景,你可以透過它來告訴 Blazor:「嘿,這點小事不用麻煩廚房了!」,從而避免不必要的畫面刷新。

OnAfterRender / OnAfterRenderAsync:元件的畫面渲染完成後呼叫。你可以用它來操作 DOM 或初始化需要畫面元素的 JavaScript 套件。它有個 firstRender 參數,讓你知道這是不是第一次上菜。

  • 就像【餐點上桌】。

Dispose:元件被銷毀時呼叫。這是非常重要的一步!你必須在這裡釋放資源、取消計時器或移除事件監聽器,否則會造成記憶體洩漏,就像餐廳打烊忘了關瓦斯一樣危險!

  • 就像【打烊休息】。

[Parameter]:由上而下的資料傳遞

就像是父元件給子元件下達的「指令」或「資料」。

# 這邊使用 [Parameter] 來定義一個可以接收文章標題的屬性。

<h3>@Title</h3>

@code {

    [Parameter]

    public string Title { get; set; } = “預設標題”;

}

  • [Parameter]:這個屬性標籤告訴 Blazor 框架,Title 這個屬性可以從外部被賦值。
  • public string Title這是一個公開的屬性,名稱是 Title,型態是 string
  • = “預設標題”這是一個可選的預設值。如果父元件沒有傳入任何值,Title 的值就會是「預設標題」。

[CascadingParameter] 級聯參數: 想像一下,如果老闆 (頂層元件) 要傳遞一個訊息「今天 WiFi 密碼是 1234」給所有員工(多層子元件),一個個傳太麻煩了。

使用級聯參數,老闆只需要宣布一次,所有在底下的員工都能自動收到這個訊息,無需層層傳遞。

使用可重複使用的元件來簡化開發流程,就像是在組裝樂高積木一樣,你不需要每次都從零開始製作一塊積木,而是可以重複使用已經設計好的積木,來快速組裝出任何你想要的形狀。

範例 : 建立一個會常常重複的「導覽列元件」

Navbar.razor
<nav class=”navbar”>
   <a href=”/” class=”brand”>我的網站</a>
   <ul>
       <li><a href=”/”>首頁</a></li>
       <li><a href=”/about”>關於我們</a></li>
       <li><a href=”/contact”>聯絡我們</a></li>
   </ul>
</nav>

有了這個元件之後,你的每個頁面程式碼就會變得非常簡單:

首頁.razor

<Navbar></Navbar>

<h1>歡迎來到首頁!</h1>

下方在新增首頁的程式碼邏輯.

關於我們.razor

<Navbar></Navbar>

<h1>關於我們</h1>
# 下方在新增關於我們的程式碼邏輯

如果子元件需要通知父元件某件事發生了(例如:按鈕被點擊),就要用 EventCallback,它就像是員工打電話給主管回報工作進度。使用 InvokeAsync 觸發回報時,Blazor 會自動幫你處理 UI 的更新,非常聰明。

  • EventCallback: 沒帶資料的回報,單純通知「事情做完了!」。
  • EventCallback<T>: 附帶資料的回報,例如回報「任務 A 完成了!」,這裡的 T 就是資料的型別。

RenderFragment 就像一個「可客製化的盒子」,你可以在父元件定義好盒子的內容,再把它整個傳給子元件顯示。你甚至可以定義多個盒子插槽。

範例:一個「警告訊息」元件

想像你要做一個網站,裡面有很多不同類型的警告訊息,但它們的外觀(邊框、背景色)都一樣,只是裡面的內容不同

步驟一:建立「可客製化」的子元件

首先,我們建立一個名為 WarningBox 的子元件。這個元件的工作就是提供一個統一的「盒子」外觀,並等待別人傳入內容
<div class=”warning-box”>
   @ChildContent
</div>

@code {
   // [Parameter] 標籤告訴 Blazor 這個屬性可以從外部接收資料。
    // RenderFragment 是一個特殊型別,用來接收可被渲染的內容。

   [Parameter]
   public RenderFragment ChildContent { get; set; }
}

<style>
   .warning-box {
       border: 2px solid #ffcc00;
       background-color: #fffacd;
       padding: 15px;
       border-radius: 5px;
       margin-bottom: 10px;
   }
</style>

步驟二:在父元件中使用這個「盒子」

現在,我們在主頁面 (Index.razor) 中使用這個 WarningBox 元件。

@page “/”
<h3>網站通知</h3>
<WarningBox>
   <p>您的密碼將在 30 天後過期。</p>
</WarningBox>
<WarningBox>
   <p>新功能已上線!</p>
   <a href=”/new-features”>點擊查看詳情</a>
</WarningBox>
<WarningBox>
   <MyOtherComponent />
</WarningBox>

  • 當我們使用 WarningBox 時,任何放在 <WarningBox> 標籤內部的內容,都會被視為 ChildContent 參數傳遞給子元件。

  • 我們傳入的內容可以是純文字、HTML 標籤,或是另一個 Blazor 元件。

  • 簡化程式碼: 我們不需要為每種警告都建立一個新元件。

  • 提高彈性: WarningBox 元件可以被用在任何地方,來顯示任何類型的內容,而不需要做任何修改。

  • 分離關注點: WarningBox 只關心「如何顯示警告框」,而主頁面只關心「要顯示什麼內容」。

單向綁定 (One-Way Binding): 資料從程式碼流向 UI。用來顯示不會變動的資料。

雙向綁定 (Two-Way Binding): 資料在程式碼和 UI 之間同步。

  • @bind=”variable” 其實是一個語法糖,它等同於 value=”@variable” @onchange=”@((ChangeEventArgs __e) => variable = __e.Value.ToString())”。

使用 @on… 指令來處理各種使用者互動,並且可以接收詳細的事件參數。

輸入更新: 如果你希望使用者一邊打字,變數就一邊更新,而不是等輸入框失去焦點才更新,可以使用@oninput

<input @oninput=”UpdateSearch” placeholder=”Search for products…” />

<p>You searched for: @searchQuery</p>

  • 輸入邏輯:

@code { 

private string searchQuery = string.Empty; 

private void UpdateSearch(ChangeEventArgs e) {  searchQuery = e.Value.ToString(); } }

點擊更新 : 新增一個按鈕並使用 @onclick 指令,每次點擊按鈕時都會增加購物車數量。

<button @onclick=”AddToCart“>Add to Cart</button>

private void AddToCart() {

   cartCount++;  }

懸浮更新 : 新增帶有 @onmouseover 指令的元素, 當使用者將滑鼠懸停在該元素上時觸發操作。

<div class=”p-4 border rounded”>

    <p @onmouseover=”ShowProductDetails“>將滑鼠懸停在產品上</p></div>

<p>@productDetails</p>

  • 建立處理懸停事件的方法:

private void ShowProductDetails() {

    productDetails = “懸停時顯示的更新詳細資訊”;  }

步驟一:設定資料類型 (Model) & 資料註釋與驗證

首先,你需要建立一個 C# 類別,這個類別就是你的表單資料模型 (Model)。它用來定義表單中每一個輸入欄位的資料類型。

  • 檔案:RegisterModel.cs

using System.ComponentModel.DataAnnotations;

public class RegisterModel

{

    // [Required] 代表這個欄位是必填的

    [Required(ErrorMessage = “使用者名稱是必填的。”)]

    public string Username { get; set; }

    // [EmailAddress] 驗證輸入是否為有效的電子郵件格式

    [Required(ErrorMessage = “電子郵件是必填的。”)]

    [EmailAddress(ErrorMessage = “請輸入有效的電子郵件地址。”)]

    public string Email { get; set; }

    // [StringLength] 驗證字串長度

    [Required(ErrorMessage = “密碼是必填的。”)]

    [StringLength(100, MinimumLength = 6, ErrorMessage = “密碼長度必須在 6 到 100 個字元之間。”)]

    public string Password { get; set; }

}

解釋: 我們建立了一個 RegisterModel 類別,它包含了使用者名稱、電子郵件和密碼三個屬性。

步驟二:建立 <EditForm> & 處理提交

現在,我們在 Blazor 元件中使用 <EditForm> 來建立表單,並將資料模型和驗證功能綁定起來。

  • 檔案:RegisterForm.razor

@page “/register”

<h3>會員註冊</h3>

<EditForm Model=”@registerModel” OnValidSubmit=”@HandleValidSubmit”>

    <DataAnnotationsValidator />

    <ValidationSummary />

    <div>

        <label>使用者名稱:</label>

        <InputText @bind-Value=”registerModel.Username” />

        <ValidationMessage For=”@(() => registerModel.Username)” />

    </div>

    <div>

        <label>電子郵件:</label>

        <InputText @bind-Value=”registerModel.Email” />

        <ValidationMessage For=”@(() => registerModel.Email)” />

    </div>

    <div>

        <label>密碼:</label>

        <InputText @bind-Value=”registerModel.Password” />

        <ValidationMessage For=”@(() => registerModel.Password)” />

    </div>

    <button type=”submit”>註冊</button>

</EditForm>

@code {

    private RegisterModel registerModel = new RegisterModel();

    // 這個方法只在表單「驗證成功」時才會被呼叫

    private void HandleValidSubmit()

    {

        Console.WriteLine(“表單驗證成功!正在處理註冊…”);

        // 這裡可以寫入將資料發送到後端的邏輯

    }

}

解釋:

  • <EditForm Model=”@registerModel”>將我們的表單與 registerModel 這個物件綁定。
  • <DataAnnotationsValidator />這個元件會告訴 Blazor,請使用我們在模型上定義的資料註釋來進行驗證。
  • <ValidationSummary />如果表單有任何驗證錯誤,這個元件會自動顯示所有錯誤訊息。
  • <InputText @bind-Value=”registerModel.Username” />:雙向綁定。當使用者在輸入框中打字時,registerModel.Username 的值會自動更新;反之亦然。
  • <ValidationMessage For=”…”>這個元件只負責顯示特定欄位的驗證錯誤訊息。

OnValidSubmit=”@HandleValidSubmit”

  • 這是最重要的部分!<EditForm> 會自動檢查所有欄位。
  • 如果所有驗證都通過,它才會呼叫 HandleValidSubmit 這個方法。
  • 如果任何一個欄位驗證失敗HandleValidSubmit 就不會被呼叫,而是會顯示錯誤訊息。

總結來說,使用 <EditForm> 來處理表單,你只需要做三件事:

  1. 建立資料模型,並加上註釋規則。
  2. <EditForm> 中綁定這個模型。
  3. OnValidSubmit 方法中,處理提交後的邏輯。

傳統網頁:你必須每隔一段時間手動刷新頁面,才能看到最新的天氣。

Blazor + SignalR:天氣資料一有更新,伺服器就會主動推送給你,你的網頁會自動更新,就像在看直播一樣。

範例「即時天氣顯示器」包含三個部分:

  1. WeatherHub.cs:伺服器端的「氣象站」,負責廣播天氣訊息。

  2. WeatherDisplay.razor:客戶端的「天氣顯示器」,負責接收並顯示天氣訊息。

  3. Program.cs:網站的設定檔,用來註冊 Hub。

WeatherHub.cs 伺服器端:天氣氣象站

// 引用 SignalR 核心套件
using Microsoft.AspNetCore.SignalR;

// WeatherHub 是一個 Hub,它管理客戶端的連線
public class WeatherHub : Hub
{
   // UpdateWeather 是一個伺服器方法,用來廣播天氣訊息
   public async Task UpdateWeather(string weatherInfo)
   {
       // Clients.All 代表所有連接到這個 Hub 的客戶端
    // SendAsync 會將 “ReceiveWeatherUpdate” 這個事件,連同天氣資訊廣播出去
       await Clients.All.SendAsync(“ReceiveWeatherUpdate”, weatherInfo);
   }
}

WeatherDisplay.razor 客戶端:天氣顯示器

@page “/weather”
@rendermode InteractiveServer 
// 啟用伺服器端互動模式,讓所有事件都在伺服器處理
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation 

// 注入導航管理器,用來獲取網址
<h3>即時天氣顯示</h3>
<p>目前天氣:@currentWeather</p>

@code {
   private string currentWeather = “正在連接氣象站…”;
    private HubConnection? hubConnection;

    // 元件初始化時,建立與伺服器的連線
   protected override async Task OnInitializedAsync()
   {
        // HubConnectionBuilder 用來建立連線
       hubConnection = new HubConnectionBuilder()
            // 設定 Hub 的網址
           .WithUrl(Navigation.ToAbsoluteUri(“/weatherhub”))
            .Build();

       // 訂閱 “ReceiveWeatherUpdate” 這個事件
    // 當氣象站廣播天氣時,這個函式會被觸發
       hubConnection.On<string>(“ReceiveWeatherUpdate”, (weatherInfo) =>
       {
            // 將收到的天氣訊息更新到變數
           currentWeather = weatherInfo;
            // 通知 Blazor 畫面需要更新
           InvokeAsync(StateHasChanged);
       });
        // 啟動連線
       await hubConnection.StartAsync();
    }

    // 關閉頁面時,確保連線被正確關閉
   public async ValueTask DisposeAsync()
   {
       if (hubConnection is not null)
       {
           await hubConnection.DisposeAsync();
       }
   }
}

Program.cs 網站設定檔

// … 其它程式碼省略 …

// 註冊 SignalR 服務
builder.Services.AddSignalR();
// 將 WeatherHub 註冊到路由,並指定它的網址
app.MapHub<WeatherHub>(“/weatherhub”);

設定頁面路由與路由約束

你可以在路由中定義參數,並加上約束條件,確保傳入的參數型別正確。

  • 使用超連結 <a> 標籤:這是最簡單的方式,和傳統網頁一樣,用於靜態導航。

  • 使用程式碼@page導航:這讓你可以在程式邏輯中控制導航,更具彈性。

當你需要在滿足某些條件後才進行導航時,這種方式就非常實用。

要使用程式碼導航,你需要先注入(Inject)NavigationManager 這個服務。

@page “/”
@inject NavigationManager navManager // 注入 NavigationManager 服務
<h1>這是網站首頁</h1>
<button @onclick=”NavigateToAbout”>點擊前往關於我們</button>

@code {
   private void NavigateToAbout()
   {
        // 使用 NavigationManager 的 NavigateTo() 方法來進行導航
       navManager.NavigateTo(“/about”);
   }
}

# 進階導航方法 – 搭配 [ Parameter]  : 在這個方法裡,你可以取得傳入的參數,並進行驗證,如果不符合條件,就立即將使用者導航到一個錯誤頁面,來防止他們看到無效的內容。

有時候資料會放在 URL 的查詢字串中 (例如 /search?keyword=blazor)。

SearchResult.razor

@page “/search”
<h3>搜尋結果: @Keyword</h3>

@code {
   // 使用 [SupplyParameterFromQuery] 來自動綁定查詢字串中的值
   [Parameter]
   [SupplyParameterFromQuery]
   public string Keyword { get; set; }
}

發佈留言