次の方法で共有


Blazor 映画データベース アプリを構築する (パート 3 - Razor コンポーネントについて)

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

この記事は、映画データベースを管理する機能を備えた ASP.NET Core Blazor の構築の基本について説明する、Blazor Web App 映画データベース アプリ チュートリアルの第 3 部です。

このチュートリアル シリーズのこのパートでは、アプリにスキャフォールディングされたプロジェクト内の Razor コンポーネントを調べます。 映画データの表示が改善されました。

Razor のコンポーネント

Blazor アプリは "Razor コンポーネント" が基になっており、単に "コンポーネント" と呼ばれることがよくあります。 "コンポーネント" とは、ページ、ダイアログ、データ入力フォームといった UI の要素です。 コンポーネントは、.NET アセンブリに組み込まれている .NET C# クラスです。

Razor は、クライアント側の UI ロジックと構成用に Razor マークアップ ページの形式 (ファイル拡張子 .razor) でコンポーネントが通常記述される方法を示しています。 Razor とは、開発者の生産性のために設計された、C# コードに HTML マークアップを結合するための構文です。

"Blazor コンポーネント" という用語を使う開発者やオンライン リソースもありますが、このドキュメントではその用語は使わず、正式名の "Razor コンポーネント" (または、単に "コンポーネント") を使っています。

Razor コンポーネントの構造には、次の一般的なパターンがあります。

  • コンポーネント定義 (.razor ファイル) の先頭にあるさまざまな Razor ディレクティブは、コンポーネント マークアップのコンパイル方法または関数を指定します。
  • 次に、Razor マークアップは、通常の HTML 要素を含む HTML のレンダリング方法を指定します。
  • 最後に、@code ブロックには、コンポーネント パラメーターやイベント ハンドラーなど、コンポーネント クラスのメンバーを定義する C# コードが含まれています。

次の Welcome コンポーネント (Welcome.razor) について考えてみましょう。

@page "/welcome"

<PageTitle>Welcome!</PageTitle>

<h1>Welcome to Blazor!</h1>

<p>@welcomeMessage</p>

@code {
    private string welcomeMessage = "We ❤️ Blazor!";
}

最初の行は、Razor コンポーネント内の重要なRazorRazor コンストラクトを表します。 Razor ディレクティブは、コンポーネント マークアップのコンパイル方法または関数を変更する @ マークアップに表示される Razor の接頭辞が付いた予約キーワードです。 @pageRazor ディレクティブは、コンポーネントのルート テンプレートを指定します。 このコンポーネントには、ブラウザーで相対 URL /welcome を使用してアクセスします。 慣例により、コンポーネント定義ファイルの先頭には、ほとんどのコンポーネントのディレクティブが配置されます。

PageTitle コンポーネントは、ページ タイトルを指定するフレームワークに組み込まれているコンポーネントです。

"Welcome to Blazor!" は、H1 見出し要素 (<h1>) のコンテンツごとに、コンポーネントの最初にレンダリングされた本文のマークアップです。

次に、C# 変数 (Razor) の前にアットマーク (@) を付けることで、welcomeMessage 構文を使用してウェルカム メッセージが表示されます。

@code ブロックには、コンポーネントの C# コードが含まれます。 welcomeMessage は、値で初期化されたプライベート文字列です。

この記事の次のセクションでは、次を説明します。

  • Web ページのナビゲーションとレイアウトの 3 つのコンポーネントである、NavMenuNavLinkMainLayout の各コンポーネントについて説明します。
  • 映画データベース エンティティに対する CRUD 操作用にスキャフォールディングによって作成されるコンポーネントについて説明します。

NavMenu コンポーネント (Components/Layout/NavMenu.razor) は、他の NavLink コンポーネントへのナビゲーション リンクをレンダリングする Razor コンポーネントを使用して、サイドバー ナビゲーションを実装します。

NavLink コンポーネントは <a> 要素のように動作しますが、active が現在の URL と一致するかどうかに基づいて href CSS クラスを切り替える点が異なります。 active クラスは、表示されているナビゲーション リンクの中でどのページがアクティブ ページであるかをユーザーが理解するのに役立ちます。 NavLinkMatch.AllMatch パラメーターに割り当てられると、アクティブな CSS クラスが現在の URL 全体と一致したときに表示されるようにコンポーネントが構成されます。

NavLink コンポーネントは、Blazor アプリで使用するBlazor フレームワークに組み込まれていますが、NavMenu コンポーネントはプロジェクト テンプレートBlazor一部にすぎません。

Components/Layout/NavMenu.razor:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">BlazorWebAppMovies</a>
    </div>
</div>

<input type="checkbox" title="Navigation menu" class="navbar-toggler" />

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
            </NavLink>
        </div>

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="weather">
                <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
            </NavLink>
        </div>
    </nav>
</div>

NavMenu コンポーネントの最初の <div> 要素のブランド リンク テキスト (<a> 要素コンテンツ) に注目してください。 ブランドを BlazorWebAppMovies から Sci-fi Movies に変更します。

- <a class="navbar-brand" href="">BlazorWebAppMovies</a>
+ <a class="navbar-brand" href="">Sci-fi Movies</a>

ユーザーが映画 Index ページにアクセスできるようにするには、ナビゲーション メニュー エントリを NavMenu コンポーネントに追加します。 <div> コンポーネントの Weather のマークアップ (NavLink) の直後に、次のマークアップを追加します。

<div class="nav-item px-3">
    <NavLink class="nav-link" href="movies">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Movies
    </NavLink>
</div>

上記の変更を行った後の最後の NavMenu コンポーネント:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">Sci-fi Movies</a>
    </div>
</div>

<input type="checkbox" title="Navigation menu" class="navbar-toggler" />

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
    <nav class="nav flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
            </NavLink>
        </div>

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="weather">
                <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
            </NavLink>
        </div>

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="movies">
                <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Movies
            </NavLink>
        </div>
    </nav>
</div>

アプリを実行して、サイドバー ナビゲーションの上部にある更新されたブランドと、映画ページ (映画) にアクセスするためのリンクを確認します。

サイドバー ナビゲーションの上部にあるブランドが

ブラウザーのウィンドウを閉じて、アプリを停止します。

ブラウザーのウィンドウを閉じ、VS Code でキーボードの Shift+F5 キーを押して、アプリを停止します。

ブラウザーのウィンドウを閉じ、コマンド シェルで Ctrl +押して、アプリを停止します。

レイアウト用の MainLayout コンポーネント

MainLayout コンポーネントはアプリの既定のレイアウトです。 MainLayout コンポーネントは、レイアウトを表すコンポーネントの基底クラスである LayoutComponentBase を継承します。 レイアウトを使用するアプリのコンポーネントは、マークアップに Body (@Body) が表示される場所にレンダリングされます。

Components/Layout/MainLayout.razor:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

MainLayout コンポーネントには、次の追加仕様が採用されています。

  • NavMenu コンポーネントがサイドバーにレンダリングされます。 コンポーネント名を含む HTML タグのみを配置して、コンポーネントを Razor マークアップ内のその場所にレンダリングする必要があることに注意してください。 これにより、コンポーネントを互いに入れ子にしたり、実装する HTML レイアウト内で入れ子にすることができます。
  • <main> 要素のコンテンツには、次のものが含まれます。
    • ASP.NET Core ドキュメントのランディング ページにユーザーを移動させる About リンク。
    • <article> (Body) パラメーターを持つ @Body 要素。レイアウトを使用するコンポーネントがレンダリングされます。
    • ハンドルされないエラーに関する通知が表示されるエラー UI (<div id="blazor-error-ui" ...>)。

MainLayout コンポーネント (Routes) では、既定のレイアウト (Components/Routes.razor) を指定します。

<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />

個々のコンポーネントは、既定以外の独自のレイアウトを自由に設定でき、同じフォルダー内の _Imports.razor ファイルを使用して、コンポーネントのフォルダー全体にレイアウトを適用できます。 これらの機能については、Blazor のドキュメントで詳しく説明しています。

作成、読み取り、更新、削除 (CRUD) の各コンポーネント

以降のセクションでは、映画 CRUD コンポーネントの構成とそのしくみについて説明します。

Index コンポーネント

Index コンポーネント定義ファイル (Components/Pages/Movies/Index.razor) を開き、ファイルの先頭にある Razor ディレクティブを調べます。

@page ディレクティブのルート テンプレートは、ページの URL が /movies であることを示します。

次の API にアクセスする @using ディレクティブが表示されます。

型 (IDbContextFactory<T>) がTであるデータベース コンテキスト ファクトリ (BlazorWebAppMoviesContext) は、@inject ディレクティブを使用してコンポーネントに挿入されます。 このファクトリ アプローチではデータベース コンテキストを破棄する必要があるため、コンポーネントに IAsyncDisposable ディレクティブを使用して @implements インターフェイスを実装します。

ページ タイトルは、Blazor フレームワークの PageTitle コンポーネントを介して設定され、H1 セクションの見出しが最初にレンダリングされる要素です。

<PageTitle>Index</PageTitle>

<h1>Index</h1>

リンクがレンダリングされ、Create/movies/create ページに移動します。

<p>
    <a href="movies/create">Create New</a>
</p>

QuickGrid コンポーネント には、ムービー エンティティが表示されます。 アイテムプロバイダーは、作成済みデータベース コンテキスト (DbSet<Movie>) から取得された CreateDbContext であり、それは挿入されたデータベース コンテキスト ファクトリ (DbFactory) に由来しています。 それぞれの映画エンティティについて、コンポーネントには映画のタイトル、リリース日、ジャンル、価格が表示されます。 列には、編集、詳細の表示、各映画エンティティの削除を行うリンクも保持されます。

<QuickGrid Class="table" Items="context.Movie">
    <PropertyColumn Property="movie => movie.Title" />
    <PropertyColumn Property="movie => movie.ReleaseDate" />
    <PropertyColumn Property="movie => movie.Genre" />
    <PropertyColumn Property="movie => movie.Price" />

    <TemplateColumn Context="movie">
        <a href="@($"movies/edit?id={movie.Id}")">Edit</a> |
        <a href="@($"movies/details?id={movie.Id}")">Details</a> |
        <a href="@($"movies/delete?id={movie.Id}")">Delete</a>
    </TemplateColumn>
</QuickGrid>

@code {
    private BlazorWebAppMoviesContext context = default!;

    protected override void OnInitialized()
    {
        context = DbFactory.CreateDbContext();
    }

    public async ValueTask DisposeAsync() => await context.DisposeAsync();
}

このコード ブロック (@code)では次の操作を行います。

  • context フィールドはデータベース コンテキストを保持し、BlazorWebAppMoviesContext として型指定されます。
  • OnInitialized ライフサイクル メソッドは、挿入されたファクトリ (CreateDbContext) から、作成されたデータベース コンテキスト (DbFactory) を context 変数に割り当てます。
  • 非同期の DisposeAsync メソッドは、コンポーネントが破棄されるときにデータベース コンテキストを破棄します。

Context のコンテキスト (TemplateColumn<TGridItem>) パラメーターが、列のコンテキスト インスタンスのパラメーター名 (movie) を指定する方法に注目してください。 コンテキスト インスタンスの名前を指定すると、マークアップがさらに読みやすくなります (コンテキストの既定の名前は、単に context)。 Movie クラス プロパティは、コンテキスト インスタンスから読み取られます。 たとえば、映画の識別子 (Id) は movie.Id で使用できます。

@と呼ばれる、かっこ付きアット (@(...)) マーク (Razor) を使用すると、各リンクの では、映画エンティティの href プロパティをリンク クエリ文字列にId () として含めることができます。 映画の識別子 (Id) が 7 の場合、その映画を編集するために href に指定された文字列の値は movies/edit?id=7 です。 リンクに従うと、"id" フィールドは、映画を読み込む Edit コンポーネントによってクエリ文字列から読み取られます。

チュートリアル シリーズの最後の部分のムービー例では、「マトリックス © QuickGrid コンポーネント は次の HTML マークアップをレンダリングします (一部の要素と属性は表示を簡略化するために存在しません)。 明示的な Razor 式と補間された文字列によって、他のページへのリンクの href 値がどのように生成されたかを確認します。 データベース内の映画の識別子は、この例では 3 になっているため、id は、3Edit、および Details ページのクエリ文字列で Delete になります。 アプリを実行すると、別の値が表示される場合があります。

<table>
    <thead>
        <tr>
            <th>Title</th>
            <th>ReleaseDate</th>
            <th>Genre</th>
            <th>Price</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>The Matrix</td>
            <td>3/29/1999</td>
            <td>Sci-fi (Cyberpunk)</td>
            <td>5.00</td>
            <td>
                <a href="movies/edit?id=3">Edit</a> |
                <a href="movies/details?id=3">Details</a> |
                <a href="movies/delete?id=3">Delete</a>
            </td>
        </tr>
    </tbody>
</table>

列名は Movie モデルのプロパティから取得されるため、リリース日の単語間にスペースがありません。 単語間にスペースを含む値で TitlePropertyColumn<TGridItem,TProp> を追加します。

- <PropertyColumn Property="movie => movie.ReleaseDate" />
+ <PropertyColumn Property="movie => movie.ReleaseDate" Title="Release Date" />

アプリを実行して、リリース日の 2 つの単語が列に表示されていることを確認します。

ブラウザーのウィンドウを閉じて、アプリを停止します。

ブラウザーのウィンドウを閉じ、VS Code でキーボードの Shift+F5 キーを押して、アプリを停止します。

ブラウザーのウィンドウを閉じ、コマンド シェルで Ctrl +押して、アプリを停止します。

Details コンポーネント

Details コンポーネント定義ファイル (Components/Pages/Movies/Details.razor) を開きます。

ファイルの先頭にある @page ディレクティブは、ページの相対 URL が /movies/details であることを示します。 前と同様に、データベース コンテキストが挿入され、API (BlazorWebAppMovies.ModelsMicrosoft.EntityFrameworkCore) にアクセスするための名前空間が提供されます。 Details コンポーネントでは、アプリの NavigationManager も挿入します。これは、コンポーネントのさまざまなナビゲーション関連操作に使用されます。

@page "/movies/details"
@using Microsoft.EntityFrameworkCore
@using BlazorWebAppMovies.Models
@inject IDbContextFactory<BlazorWebAppMovies.Data.BlazorWebAppMoviesContext> DbFactory
@inject NavigationManager NavigationManager

映画エンティティの詳細は、クエリ文字列内の識別子 (Id) によって特定される映画が表示用に読み込まれている場合にのみ表示されます。 その映画が movie に存在するかどうかは、@ifRazor ステートメントで確認されます。

@if (movie is null)
{
    <p><em>Loading...</em></p>
}

映画が読み込まれると、説明リスト (MDN ドキュメント) として、次の 2 つのリンクと共に表示されます。

  • 最初のリンクを使用すると、ユーザーはエンティティを編集することができます。
  • 2 番目のリンクを使用すると、ユーザーは映画の Index ページに戻ることができます。

表示用の Razor マークアップを簡素化するために、次の例では CSS クラスは示されていません。

<dl>
    <dt>Title</dt>
    <dd>@movie.Title</dd>
    <dt>ReleaseDate</dt>
    <dd>@movie.ReleaseDate</dd>
    <dt>Genre</dt>
    <dd>@movie.Genre</dd>
    <dt>Price</dt>
    <dd>@movie.Price</dd>
</dl>
<div>
    <a href="@($"/movies/edit?id={movie.Id}")">Edit</a> |
    <a href="@($"/movies")">Back to List</a>
</div>
</div>

映画公開日の説明用語要素 (<dt>) の内容にスペースを追加して、単語を区切ります。

- <dt class="col-sm-2">ReleaseDate</dt>
+ <dt class="col-sm-2">Release Date</dt>

コンポーネントの @code ブロックの C# コードを調べます。

private Movie? movie;

[SupplyParameterFromQuery]
private int Id { get; set; }

protected override async Task OnInitializedAsync()
{
    using var context = DbFactory.CreateDbContext();
    movie = await context.Movie.FirstOrDefaultAsync(m => m.Id == Id);

    if (movie is null)
    {
        NavigationManager.NavigateTo("notfound");
    }
}

movie 変数は Movie 型のプライベート フィールドです。これは null 参照型 (?) です。つまり、movienull に設定される可能性があります。

Id は、の存在によりコンポーネントのクエリ文字列から提供される[SupplyParameterFromQuery]です。 識別子が見つからない場合、Id の既定値は 0 (0) になります。

OnInitializedAsync は、これまで見てきた最初のコンポーネント ライフサイクル メソッドです。 このメソッドは、コンポーネントの読み込み時に実行されます。 データベースセット (FirstOrDefaultAsync) に対して DbSet<Movie> が呼び出され、クエリ文字列で設定された Id パラメーターと等しい Id を持つ映画エンティティが取得されます。 movienull の場合は、NavigationManager.NavigateTo を使用して notfound エンドポイントに移動します。

アプリに実際の notfound エンドポイント (Razor コンポーネント) はありません。 サーバー側レンダリング (SSR) を導入する場合、Blazor には 404 (Not Found) 状態コードを返すメカニズムがありません。 一時的な回避策として、存在しないエンドポイントに移動することで 404 が生成されます。 このスキャフォールディングされたコードは、エンティティが見つからない場合に適切な結果をさらに実装するためのものです。 たとえば、コンポーネントが、サポート チームに問い合わせを提出できるページにユーザーを誘導したり、挿入された NavigationManager および NavigationManager.NavigateTo コードを削除して、エンティティが見つからなかったというメッセージを表示する Razor マークアップおよびコードに置き換えたりすることができます。

Create コンポーネント

Create コンポーネント定義ファイル (Components/Pages/Movies/Create.razor) を開きます。

このコンポーネントは、EditFormと呼ばれる組み込みコンポーネントを使用します。このコンポーネントはユーザー入力用のフォームをレンダリングし、検証機能を含んでいます。

次の例では、表示を簡素化するために CSS クラスが存在しません。

<EditForm method="post" Model="Movie" OnValidSubmit="AddMovie" FormName="create" Enhance>
    <DataAnnotationsValidator />
    <ValidationSummary role="alert" />
    <div>
        <label for="title">Title:</label> 
        <InputText id="title" @bind-Value="Movie.Title" /> 
        <ValidationMessage For="() => Movie.Title" /> 
    </div>
    <div>
        <label for="releasedate">ReleaseDate:</label> 
        <InputDate id="releasedate" @bind-Value="Movie.ReleaseDate" /> 
        <ValidationMessage For="() => Movie.ReleaseDate" /> 
    </div>
    <div>
        <label for="genre">Genre:</label> 
        <InputText id="genre" @bind-Value="Movie.Genre" /> 
        <ValidationMessage For="() => Movie.Genre" /> 
    </div>
    <div>
        <label for="price">Price:</label> 
        <InputNumber id="price" @bind-Value="Movie.Price" /> 
        <ValidationMessage For="() => Movie.Price" /> 
    </div>
    <button type="submit">Create</button>
</EditForm>

ムービーのリリース日のラベル要素 (<label>) の内容にスペースを追加して、単語を区切ります。

- <label for="releasedate" class="form-label">ReleaseDate:</label>
+ <label for="releasedate" class="form-label">Release Date:</label>

Model パラメーターには、モデル (この場合は Movie) が割り当てられます。 OnValidSubmit は、フォームが送信されデータが有効な場合に呼び出すメソッド (AddMovie) を指定します。 慣例により、ページに複数のフォームが存在する場合にフォームの競合を防ぐために、すべてのフォームに FormName を割り当てる必要があります。 Enhance フラグは、ページ全体の再読み込みを実行せずにフォームを送信するサーバー側レンダリング (SSR) のための Blazor 機能をアクティブにします。

検証用:

  • DataAnnotationsValidator では、データ注釈検証のサポートが追加されます。これについては、このチュートリアル シリーズで後ほど説明します。
  • ValidationSummary コンポーネントは、検証メッセージの一覧を表示します。
  • ValidationMessage<TValue> コンポーネントでは、フォームのフィールドの検証メッセージを保持します。

Blazor には、EditForm や、InputTextInputDate<TValue>InputNumber<TValue> などのさまざまな入力コンポーネントをはじめ、フォームの作成に役立つ複数のフォーム要素コンポーネントが含まれています。 各入力コンポーネントは、@bind-ValueRazor 構文を使用してモデル プロパティにバインドされます。ここで、Value は各入力コンポーネントのプロパティです。

コンポーネントの @code ブロックでは、Movie を通じてフォームに関連付けられた [SupplyParameterFromForm] コンポーネントパラメーターが C# コードに含まれています。

AddMovie メソッド:

  • フォームが送信されると呼び出されます。
  • フォームの検証に合格した場合、フォームのモデル (Movie) にバインドされた映画データを追加します。
  • SaveChangesAsync は、映画を保存するためにデータベース コンテキストで呼び出されます。
  • NavigationManager は、ユーザーを映画の Index ページに戻すために使用されます。
@code {
    [SupplyParameterFromForm]
    private Movie Movie { get; set; } = new();

    private async Task AddMovie()
    {
        using var context = DbFactory.CreateDbContext();
        context.Movie.Add(Movie);
        await context.SaveChangesAsync();
        NavigationManager.NavigateTo("/movies");
    }
}

警告

このチュートリアルでは、それはアプリの問題ではありませんが、フォーム データをエンティティ データ モデルにバインドすると、オーバーポスティング攻撃の影響を受ける可能性があります。 このトピックに関する追加情報は、この記事で後ほど取り上げます。

Delete コンポーネント

Delete コンポーネント定義ファイル (Components/Pages/Movies/Delete.razor) を開きます。

映画公開日の説明用語要素 (<dt>) の内容にスペースを追加して、単語を区切ります。

- <dt class="col-sm-2">ReleaseDate</dt>
+ <dt class="col-sm-2">Release Date</dt>

Razor の送信ボタンの EditForm マークアップを調べます (わかりやすくするために CSS クラスが削除されています)。

<button type="submit" disabled="@(movie is null)">Delete</button>

[Delete] ボタンは、明示的なdisabled式 () を使用して、ムービーの存在 (nullではない) に基づいてRazor@(...)を設定します。

@code ブロックの C# コードでは、DeleteMovie メソッドはムービーを削除し、変更をデータベースに保存して、ユーザーをムービー Index ページに移動します。 "ムービー" フィールド (movie!) の感嘆符は、null 許容演算子 (C# 言語リファレンス) であり、movie に対する null 許容警告を抑制します。

private async Task DeleteMovie()
{
    using var context = DbFactory.CreateDbContext();
    context.Movie.Remove(movie!);
    await context.SaveChangesAsync();
    NavigationManager.NavigateTo("/movies");
}

Edit コンポーネント

Edit コンポーネント定義ファイル (Components/Pages/Movies/Edit.razor) を開きます。

ムービーのリリース日のラベル要素 (<label>) の内容にスペースを追加して、単語を区切ります。

- <label for="releasedate" class="form-label">ReleaseDate:</label>
+ <label for="releasedate" class="form-label">Release Date:</label>

コンポーネントは、EditForm コンポーネントと同様の Create を使用します。

ムービー エンティティの識別子 Id は、フォームの隠しフィールドに格納されます。

<input type="hidden" name="Movie.Id" value="@Movie.Id" />

@code ブロックの C# コードを調べます。

private async Task UpdateMovie()
{
    using var context = DbFactory.CreateDbContext();
    context.Attach(Movie!).State = EntityState.Modified;

    try
    {
        await context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie!.Id))
        {
            NavigationManager.NavigateTo("notfound");
        }
        else
        {
            throw;
        }
    }

    NavigationManager.NavigateTo("/movies");
}

private bool MovieExists(int id)
{
    using var context = DbFactory.CreateDbContext();
    return context.Movie.Any(e => e.Id == id);
}

ムービー エンティティの EntityStateModified に設定されます。これは、エンティティがコンテキストによって追跡され、データベースに存在し、そのプロパティ値の一部またはすべてが変更されることを示します。

コンカレンシー例外があり、変更が保存された時点でムービー エンティティが存在しなくなった場合、コンポーネントは存在しないエンドポイント (notfound) にリダイレクトされ、結果として 404 (Not Found) 状態コードが返されます。 このコードを変更して、ムービーがデータベースに存在しなくなったことをユーザーに通知したり、専用の Not Found コンポーネントを作成して、ユーザーをそのエンドポイントに移動したりできます。 ムービーが存在し、コンカレンシー例外がスローされた場合 (たとえば、別のユーザーがエンティティを既に変更している場合など)、throw ステートメント (C# 言語リファレンス) を使用してコンポーネントによって例外が再スローされます。 EF Core アプリでの Blazor のコンカレンシーの処理に関する追加のガイダンスは、Blazor ドキュメントで提供されています。

警告

このチュートリアルでは、それはアプリの問題ではありませんが、フォーム データをエンティティ データ モデルにバインドすると、オーバーポスティング攻撃の影響を受ける可能性があります。 このトピックに関する追加情報は、次のセクションに表示されます。

過剰投稿攻撃を軽減する

Create コンポーネントや Edit コンポーネントのフォームのように静的にレンダリングされたサーバー側フォームは、一括割り当て攻撃とも呼ばれるオーバーポスティング攻撃に対して脆弱になる可能性があります。 オーバーポスト攻撃は、悪意のあるユーザーが、レンダリングされたフォームの一部ではなく、開発者がユーザーに修正を許可しないプロパティのデータを処理する HTML フォームの POST をサーバーに発行したときに発生します。 "オーバーポスティング" という用語は、文字どおり、悪意のあるユーザーがフォームで "過剰に" ポストすることを意味します。

このチュートリアルの Create コンポーネントと Edit コンポーネントの例では、Movie モデルには作成操作と更新操作の制限付きプロパティが含まれていないため、オーバーポスティングは問題になりません。 ただし、今後作成したり修正したりする静的 SSR ベースの Blazor フォームで作業する場合は、オーバーポスティングに注意することが重要です。

オーバーポスティングを軽減するために、作成 (挿入) および更新操作でフォームとデータベースに対して個別のビュー モデルまたはデータ転送オブジェクト (DTO) を使用することをお勧めします。 フォームが送信されると、ビュー モデルまたは DTO のプロパティのみがコンポーネントと C# コードによって使用され、データベースが変更されます。 悪意のあるユーザーによって含まれる余分なデータは破棄されるため、悪意のあるユーザーによるオーバーポスティング攻撃を防ぎます。

完成したサンプルを使用したトラブルシューティング

チュートリアルの実行中にテキストから解決できない問題が発生した場合は、コードを、 Blazor サンプル リポジトリの完成したプロジェクトと比較します。

Blazor サンプル GitHub リポジトリ (dotnet/blazor-samples)

最新バージョンのフォルダーを選択します。 このチュートリアルのプロジェクトのサンプル フォルダーには、BlazorWebAppMovies という名前が付けられています。

その他のリソース

次のステップ