昨日、FetchData.razorのコードが長すぎたため、手間を省くために@code部分を別ファイルに抽出しました。
Blazorでは、partial classとComponentBaseの2つの方法が提供されています。どちらにも長所と短所がありますが、筆者はComponentBaseを好みます。これは個人の使用習慣によります。
まずpartial classを見てみましょう。FetchData.razorを開き、先ほど述べた@using BlazorServer.Dataを_Imports.razorに移動します。次に、FetchData.razor.csというクラスを作成し、publicとclassの間にpartial修飾子を追加します。その後、FetchData.razorの@codeブロックを切り取ってFetchData.razor.csに貼り付け、少し修正します。これは従来のC#コードとほとんど変わりません。F5キーを押すと、10件の天気データが表示されます。
元のFetchData.razor:

C#コード抽出後のコードスクリーンショット:

ページの表示は変わりません:

次にComponentBaseを見てみましょう。まずpartial class内のコードをコピーします。次に、FetchDataBase.csという新しいクラスを作成し、コピーしたコードを貼り付けて少し修正します。その後、FetchData.razor.csというpartial classを削除します。2つのモードは共存できないからです。ComponentBaseを継承し、[Inject]を追加した以外に違いはほとんどありません。ここでの[Inject]は、FetchData.razor.csで@inject WeatherForecastService ForecastServiceを使用するのと同じです。F5キーを押してデバッグモードを起動し、14行目の左側をクリックしてブレークポイントを設定すると、天気データが取得されていることが確認できます。
ComponentBaseの使用:

次に、独自のComponentを作成しましょう。まず不要なComponentを削除し、Program.csと_Import.razorから天気関連のusingと登録も削除します。これらのファイルの場所を忘れた場合は、Componentを削除した後、マウスをBlazorServerプロジェクトに合わせて右クリックし、「プロジェクトのリビルド」を選択すれば、Visual Studioがエラー箇所を教えてくれます。
不要なコンポーネントなどのファイルを削除:

以前、ユーザーがログを書けるWebサイトを作成すると述べました。そのためには基本的な入力フィールドが必要であり、ログの単位は1記事あたりとします。まずModelsフォルダを作成し、PostModel型を確立します。内容は3つのプロパティのみのシンプルなものです。次にPagesフォルダにPost.razorとPostBase.razor.csを作成します。最後にNavMenu.razorのリンクを1つだけ残し、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にダミーデータを1件置きます。ここでOnInitializedAsync()というメソッドが見られます。これは、このコンポーネントのライフサイクルが開始されると、内部の処理が最初に実行されることを意味します。他にもOnAfterRenderAsync、OnParametersSetAsyncなどがあり、overrideと入力してスペースキーを押すとこれらのメソッドが表示されます。同期と非同期の両方のモードがあります。これらのメソッドについては、機会があれば説明します。

一方、Post.razorではEditFormというコンポーネントを使用しています。コンパイル後はHTMLのform要素に相当します。内部には3つのinput要素があります。Blazorには対応するInputコンポーネントも用意されており、公式ドキュメントでそれぞれのコンパイル後の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>
上記と下記の2つの記述方法の比較:
@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コンポーネント:

この時点でWebページを開くと、PostBase.razor.csで定義した値が表示されています。これはどのように結びついているのでしょうか? その理由は、EditFormのModelプロパティと3つの<Input>コンポーネントの@bind-Valueプロパティにあります。これはBlazorに対して、「私のModelとその内部の値をこのEditFormにバインドする」と指示しています。これがバックエンドコードに接続されていれば、Webページ上で入力された内容はイベントトリガーを経て、バックエンドに送信されて処理されます。

ただし、<Input>のデフォルトのCSSスタイルはあまり見栄えが良くないので、まず基本的なBootstrapスタイルを適用します。Idは通常ユーザーに入力させないため、ここではコメントアウトします。そして、テーブル検証メカニズムを追加します。ユーザーが適当に入力してフォームを送信できないようにするためです。しかし、自分で多数の検証メカニズムを記述したくない場合はどうすればよいでしょうか? BlazorのDataAnnotationsValidatorとValidationSummaryの2つのコンポーネントを試してみてください。

まず、PostModelのTitleとContentに2つの属性を追加します。Requiredは必須入力を意味し、MaxLengthとMinLengthは最大文字数と最小文字数を制限します。エラーメッセージをカスタマイズすることもできます。次に、EditForm内にDataAnnotationsValidatorとValidationSummaryの2つのコンポーネントを追加します。1つ目は各Inputを検証し、2つ目はエラーメッセージをテーブルの上部に表示します。


しかし、検証メカニズムのデフォルトのCSSスタイルが気に入らない場合はどうすればよいでしょうか? Blazorにはカスタマイズ方法も用意されています。まず、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:クリックして表示