(6/30)みんなで学ぶBlazor:C#コードの分離

(6/30)みんなで学ぶBlazor:C#コードの分離

昨日`FetchData.razor`のコードが長すぎると感じたので、便宜上、`@code`の部分を抽出して別ファイルにしましょう。

最終更新 2021/12/12 21:59
StrayaWorker
読了目安 4 分
カテゴリ
Blazor
テーマ
みんなで学ぶBlazorシリーズ
タグ
.NET C# ASP.NET Core Blazor

昨日、FetchData.razorのコードが長すぎたため、手間を省くために@code部分を別ファイルに抽出しました。

Blazorでは、partial classComponentBaseの2つの方法が提供されています。どちらにも長所と短所がありますが、筆者はComponentBaseを好みます。これは個人の使用習慣によります。

まずpartial classを見てみましょう。FetchData.razorを開き、先ほど述べた@using BlazorServer.Data_Imports.razorに移動します。次に、FetchData.razor.csというクラスを作成し、publicclassの間にpartial修飾子を追加します。その後、FetchData.razor@codeブロックを切り取ってFetchData.razor.csに貼り付け、少し修正します。これは従来のC#コードとほとんど変わりません。F5キーを押すと、10件の天気データが表示されます。

元のFetchData.razor:

元のFetchData.razor

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

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

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

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

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

ComponentBaseの使用:

ComponentBaseの使用

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

不要なコンポーネントなどのファイルを削除:

不要なコンポーネントなどのファイルを削除

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

PostBase

一方、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コンポーネント:

Input Component

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

Postページの表示

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

スタイルの追加

まず、PostModelのTitleとContentに2つの属性を追加します。Requiredは必須入力を意味し、MaxLengthMinLengthは最大文字数と最小文字数を制限します。エラーメッセージをカスタマイズすることもできます。次に、EditForm内にDataAnnotationsValidatorValidationSummaryの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.csEditContext型のフィールドEditContextを追加します。フィールドEditContextを初期化し、元のPostフィールドを渡します。そして、EditContextのメソッドSetFieldCssClassProviderを呼び出して、CustomFieldClassProviderインスタンスを追加します。

カスタムスタイルの関連付け

最後に最も重要な手順として、Post.razorEditFormModelパラメータを削除し、代わりにEditContextパラメータに変更します。その値は先ほどのEditContextフィールドです。

ModelをEditContextに置き換え

この状態で再度フォームを送信すると、textareaの赤い枠線が消え、フォントがtext-dangerの赤色になり、正しいフィールド値はtext-primaryの青色になります。CustomFieldClassProviderを振り返ってみると、EditContextEditFormの内容を指し、fieldIdentifierは現在検証中のInputタグです。EditContextが呼び出すメソッドGetValidationMessagesfieldIdentifierから何らかの情報を取得した場合、それは誤ったフィールド値を意味し、取得しなければ正しいフィールド値です。これがBlazorがFormをカスタマイズする方法です。

カスタマイズされたデータ検証メッセージ

引用:

  1. Split HTML And C# Code In Blazor Using Either Partial Class Or ComponentBase Class
  2. ASP.NET Core Blazor forms and validation
  3. Custom validation CSS class attributes

注:本稿のコードは .NET 6 + Visual Studio 2022 でリファクタリングされています。原文リンクからリファクタリング後のコードと比較して学習できます。お読みいただきありがとうございます。原作者をサポートしてください。

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2021/12/25

(29/30)みんなで学ぶBlazor:Blazor単体テスト

システム開発において最も退屈なプロセスは、おそらくバグ修正です。特に、null オブジェクトにアクセスしようとするエラー(`Object reference not set to an instance of an object.`)は、多くの初心者が最初に直面する問題です。退屈なバグ修正から解放されるために、この記事では「単体テスト」を紹介します。

続きを読む
同じカテゴリ / 同じタグ 2021/12/25

(28/30)みんなで学ぶBlazor:ポリシーベースの認可

以前に「ASP.NET Core Identity」は「Claim」ベースの検証を使用すると述べましたが、実は「ASP.NET Core Identity」には異なる種類の認可方法があります。最も簡単な「ログイン認可」「ロール認可」「Claim認可」ですが、これらはすべて同じ方法で実現されています:原則認可(ポリシーベースの認可)です。

続きを読む