昨天看到FetchData.razor的程式碼太長了,為求方便,我們把@code的部分抽取出來另成一個檔案。
Blazor 提供了兩個方法:partial class跟ComponentBase,同樣兩者各有優缺點,筆者偏好ComponentBase,看個人使用習慣。
先來看partial class,我們打開FetchData.razor,把前面提到的@using BlazorServer.Data移到_Imports.razor,再建立一個class叫做FetchData.razor.cs,在public跟class中間加上partial修飾詞,接著把FetchData.razor的@code區塊剪下貼到FetchData.razor.cs後稍作修改,可以看出跟舊的 C#程式碼沒什麼差別。我們按下 F5,一樣看到 10 條天氣資料。
原 FetchData.razor:

提取 C#程式碼後程式碼截圖:

頁面展示不變:

接著來看ComponentBase,先把partial class裡面的程式碼複製下來,接著建立一個新 class 取名為FetchDataBase.cs,將剛剛的程式碼貼上後稍做修改,再刪除FetchData.razor.cs這個 partial class,因為兩種模式不能並存。可以看到除了繼承ComponentBase跟加上[Inject]外,幾乎沒有差別,這裡的[Inject]相當於在FetchData.razor.cs使用@inject WeatherForecastService ForecastService,我們按下 F5 啟動偵錯模式,在 14 行左邊按一下點擊中斷點,可以看到一樣取得了天氣資料。
使用 ComponentBase:

接著開始做自己的 Component 吧!先將不必要的 Component 刪除,Program.cs 和_Import.razor 裡面跟天氣有關的 using 及註冊同樣刪除,如果忘記這兩個檔案在哪裡,可以在刪除 Component 後將滑鼠移到專案 BlazorServer 點右鍵,選取重建專案,Visual Studio 就會告訴你哪裡出錯了。
刪除多餘元件等檔案:

之前說過要完成讓使用者寫日誌的網站,所以需要最基本的輸入框,而日誌的單位就以一篇計算。首先建立 Models 資料夾,建立PostModel類型,裡面很簡單只有 3 個屬性,接著在 Pages 資料夾建立Post.razor跟PostBase.razor.cs,最後將NavMenu.razor的連線留下一個,href屬性的值改為Post。
/Models/PostModel.cs
namespace BlazorServer.Models;
public class PostModel
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
}
/Shared/NavMenu.razor:
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="/Post" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Post
</NavLink>
</div>
</nav>
</div>
PostModel是用來承接資料的容器,目前沒有 Service,所以在PostBase.razor.cs放一筆假資料,這邊可以看到一個方法OnInitializedAsync(),代表當這個 Component 生命週期開始,裡面的事情就會先做,其他還有OnAfterRenderAsync、OnParametersSetAsync等等,只要先輸入 override 再按一下空白鍵,就可以看到這些方法,且也有同步跟非同步兩種模式,這些方法有機會再說明。

而Post.razor用了EditForm這個 Component,編譯過後相當於 html 的 form 元素,裡面還有 3 個 input 元素,Blazor 也有提供相對應的 Input Component,可以從官方文件看到分別編譯後的 html 元素。
@page "/Post" @inherits PostBase
<EditForm Model="@Post">
<input type="number" value="@Post!.Id" />
<input type="text" value="@Post!.Title" />
<textarea value="@Post!.Content"></textarea>
<button type="submit">Submit</button>
</EditForm>
上下兩種寫法對比:
@page "/Post" @inherits PostBase
<EditForm Model="@Post">
<InputNumber @bind-Value="Post!.Id"></InputNumber>
<InputText @bind-Value="Post!.Title"></InputText>
<InputTextArea @bind-Value="Post!.Content"></InputTextArea>
<button type="submit">Submit</button>
</EditForm>
Blazor 提供相對應的 Input Component:

這時候打開網頁來看,可以看到上面出現了我們定義在PostBase.razor.cs的值,但這是怎麼連接起來的呢?原因就是 EditForm 的 Model 屬性及 3 個<Input>Component 的屬性@bind-Value,這裡在告訴 Blazor:我的 Model 跟裡面的值要跟這個 EditForm 綁在一起,如果這裡有跟後端程式碼連接,網頁上輸入的內容經過事件觸發後,就會提交後端處理。

不過<Input>預設的 CSS 樣式不太好看,我們先套用基本的 boostrap 樣式;因為 Id 通常不會讓使用者輸入,所以這邊先註解,然後再加入表格驗證機制,畢竟不能讓使用者隨便輸入就提交表格,但如果不想自己寫一堆驗證機制呢?可以試試看 Blazor 的DataAnnotationsValidator及ValidationSummary兩個 Component。

我們先在 PostModel 的 Title 跟 Content 加上兩個 Attribute,Required 代表必填,MaxLength 及 MinLength 則是限制最大及最小字數,還可以自訂錯誤訊息。接著在 EditForm 裡面加上那兩個 ComponentDataAnnotationsValidator 及 ValidationSummary,第一個是驗證各個 Input,第二個則是將錯誤訊息顯示在表格上方。


但如果不喜歡驗證機制預設的 CSS 樣式呢?Blazor 也提供了客製化的方法。先建立一個class名為CustomFieldClassProvider且繼承FieldCssClassProvider,多載方法GetFieldCssClass,裡面的內容待會再說。
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorServer;
public class CustomFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "text-primary" : "text-danger";
}
}
接著在PostBase.razor.cs加入一個型別為EditContext的欄位EditContext,初始化欄位EditContext,將原本的Post欄位丟進去,然後呼叫EditContext的方法SetFieldCssClassProvider,添加CustomFieldClassProvider實例。

最後最重要的一步,在Post.razor將EditForm的Model參數移除,改為EditContext參數,裡面的值就是剛剛的EditContext欄位。

這時候再提交表單一次,可以看到 textarea 的紅色外框消失了,字體也變成text-danger的紅色,而正確的欄位值則是變成text-primary的藍色。我們再回頭看CustomFieldClassProvider,原來EditContext指的就是EditForm的內容,fieldIdentifier則是當前驗證的Input標籤,如果EditContext呼叫的方法GetValidationMessages有在fieldIdentifier取得任何資訊,代表這是錯誤的欄位值,沒有則是正確欄位值,這就是 Blazor 幫我們客製化 Form 的做法。

引用:
- Split HTML And C# Code In Blazor Using Either Partial Class Or ComponentBase Class
- ASP.NET Core Blazor forms and validation
- Custom validation CSS class attributes
註:本文程式碼透過 .NET 6 + Visual Studio 2022 重構,可點擊原文連結與重構後程式碼比較學習,謝謝閱讀,支持原作者
- 本文 Markdown:點擊瀏覽