パート 5、ASP.NET Core アプリでの生成済みページの更新

注

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

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。

スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は Release Date のように 2語で表記する必要があります。

Chrome でムービー アプリケーションが開きます。

モデルを更新する

次の強調表示されているコードを使用して、Models/Movie.cs を更新します。

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

前のコードでは:

  • [Column(TypeName = "decimal(18, 2)")] データ注釈により、Entity Framework Core で Price をデータベース内の通貨に正しくマップできるようになります。 詳細については、「Data Types」(データ型) を参照してください。
  • [Display] 属性では、フィールドの表示名を指定します。 上記のコードでは、Release Date ではなく ReleaseDate です。
  • [DataType] 属性では、データの型 (Date) を指定します。 フィールドに格納されている時刻情報は表示されません。

DataAnnotations については、次のチュートリアルで説明します。

Pages/MoviePages に移動し、[編集] リンクの上にマウス ポインターを合わせると、ターゲット URL が表示されます。

[編集] リンクの上にマウスを移動したブラウザー ウィンドウに、 https://localhost:1234/MoviePages/Edit/5 のリンク URL が表示されます。

[編集]、[詳細]、および [削除] の各リンクは、Pages/MoviePages/Index.cshtml ファイル内の Anchor Tag Helper によって生成されます。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーによって、HTML href 属性値が Razor ページ (ルートは相対)、asp-page、およびルート ID (asp-route-id) から動的に生成されます。 詳細については、「ページの URL の生成」をご覧ください。

ブラウザーから [ソースの表示] を使用して、生成されたマークアップを確認します。 生成された HTML の部分を以下に示します。

<td>
  <a href="/MoviePages/Edit?id=1">Edit</a> |
  <a href="/MoviePages/Details?id=1">Details</a> |
  <a href="/MoviePages/Delete?id=1">Delete</a>
</td>

動的に生成されたリンクでは、クエリ文字列でムービー ID が渡されます。 たとえば、https://localhost:7247/MoviePages/Details?id=1 の ?id=1 です。

ルート テンプレートの追加

{id:int} ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の Razor ページを更新します。 これらの各ページのページ ディレクティブを @page から @page "{id:int}" に変更します。 アプリを実行してから、ソースを表示します。

生成される HTML では、次のように URL のパス部分に ID を追加します。

<td>
  <a href="/MoviePages/Edit/1">Edit</a> |
  <a href="/MoviePages/Details/1">Details</a> |
  <a href="/MoviePages/Delete/1">Delete</a>
</td>

整数を含まない {id:int} ルート テンプレートを含むページへの要求は、HTTP 404 (見つかりません) エラーを返します。 たとえば、 https://localhost:7247/MoviePages/Details は 404 エラーを返します。 ID を省略可能にするには、ルート制約に ? を追加します。

@page "{id:int?}"

@page "{id:int?}" の動作をテストします:

  1. Pages/MoviePages/Details.cshtml のページ ディレクティブを @page "{id:int?}" に設定してください。
  2. public async Task<IActionResult> OnGetAsync(int? id) のブレーク ポイントを、Pages/MoviePages/Details.cshtml.cs で設定します。
  3. https://localhost:7247/MoviePages/Details/ に移動します。

@page "{id:int}" ディレクティブでは、ブレークポイントがヒットすることはありません。 ルーティング エンジンは、HTTP 404 を返します。 @page "{id:int?}" を使用すると、OnGetAsync メソッドから NotFound (HTTP 404) が返されます。

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id is null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
    if (movie is null)
    {
        return NotFound();
    }
    else
    {
        Movie = movie;
    }

    return Page();
}

コンカレンシー例外処理の確認

OnPostAsync メソッドを Pages/MoviePages/Edit.cshtml.cs ファイルで確認します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.Id == id);
}

上のコードでは、一方のクライアントがムービーを削除し、もう一方のクライアントがムービーに変更を投稿した場合に、コンカレンシーの例外を検出します。

catch ブロックをテストするには:

  1. catch (DbUpdateConcurrencyException) にブレークポイントを設定する。
  2. ムービーの [編集] を 選択し、変更を加えますが、[ 保存] を選択しないでください。
  3. 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
  4. 前のブラウザー ウィンドウで、ムービーに変更を投稿します。

運用コードでは、コンカレンシーの競合を検出することが必要な場合があります。 詳細については、「 コンカレンシーの競合の処理」を参照してください。

レビューの投稿とバインディング

Pages/MoviePages/Edit.cshtml.cs ファイルを調べます。

public class EditModel : PageModel
{
    private readonly RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id is null)
        {
            return NotFound();
        }

        var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie is null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.Id == id);
    }

MoviePages/Edit ページに対して HTTP GET 要求を行う場合(例: https://localhost:7247/MoviePages/Edit/3:

  • OnGetAsync メソッドはデータベースからムービーをフェッチし、Page メソッドを返します。
  • Page メソッドを使用すると、Pages/MoviePages/Edit.cshtmlRazor ページがレンダリングされます。 Pages/MoviePages/Edit.cshtml ファイルにはモデル ディレクティブ @model RazorPagesMovie.Pages.MoviePages.EditModel が含まれています。これにより、ページで movie モデルが使用できるようになります。
  • 編集フォームには、ムービーの値が表示されます。

MoviePages/Edit ページを投稿する場合:

  • ページ上のフォーム値は、 Movie プロパティにバインドされます。 [BindProperty] 属性により、モデル バインドが有効になります。

    [BindProperty]
    public Movie Movie { get; set; }
    
  • たとえば、モデル状態でエラーが発生 ReleaseDate 日付に変換できない場合、フォームは送信された値と共に再表示されます。

  • モデル エラーがない場合、ムービーは保存されます。

[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] OnPostAsync ページの HTTP POST Razor メソッドも [編集] OnPostAsync ページの Razor メソッドと同様のパターンに従います。

次のステップ

スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は Release Date のように 2語で表記する必要があります。

Chrome で開かれているムービー アプリケーション

モデルを更新する

次の強調表示されているコードを使用して、Models/Movie.cs を更新します。

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

前のコードでは:

  • [Column(TypeName = "decimal(18, 2)")] データ注釈により、Entity Framework Core で Price をデータベース内の通貨に正しくマップできるようになります。 詳細については、「Data Types」(データ型) を参照してください。
  • [Display] 属性では、フィールドの表示名を指定します。 上記のコードでは、Release Date ではなく ReleaseDate です。
  • [DataType] 属性では、データの型 (Date) を指定します。 フィールドに格納されている時刻情報は表示されません。

DataAnnotations については、次のチュートリアルで説明します。

Pages/Movies に移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:1234/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、および [削除] の各リンクは、Pages/Movies/Index.cshtml ファイル内の Anchor Tag Helper によって生成されます。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーによって、HTML href 属性値が Razor ページ (ルートは相対)、asp-page、およびルート ID (asp-route-id) から動的に生成されます。 詳細については、「ページの URL の生成」をご覧ください。

ブラウザーから [ソースの表示] を使用して、生成されたマークアップを確認します。 生成された HTML の部分を以下に示します。

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動的に生成されたリンクでは、クエリ文字列でムービー ID が渡されます。 たとえば、https://localhost:5001/Movies/Details?id=1 の ?id=1 です。

ルート テンプレートの追加

{id:int} ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の Razor ページを更新します。 これらの各ページのページ ディレクティブを @page から @page "{id:int}" に変更します。 アプリを実行してから、ソースを表示します。

生成される HTML では、次のように URL のパス部分に ID を追加します。

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

{id:int} ルート テンプレートを持つページへの、整数を含まない要求では、HTTP 404 (見つかりません) エラーが返されます。 たとえば、 https://localhost:5001/Movies/Details は 404 エラーを返します。 ID を省略可能にするには、ルート制約に ? を追加します。

@page "{id:int?}"

@page "{id:int?}" の動作をテストします:

  1. Pages/Movies/Details.cshtml のページ ディレクティブを @page "{id:int?}" に設定してください。
  2. public async Task<IActionResult> OnGetAsync(int? id) のブレーク ポイントを、Pages/Movies/Details.cshtml.cs で設定します。
  3. https://localhost:5001/Movies/Details/ に移動します。

@page "{id:int}" ディレクティブでは、ブレークポイントがヒットすることはありません。 ルーティング エンジンは、HTTP 404 を返します。 @page "{id:int?}" を使用すると、OnGetAsync メソッドから NotFound (HTTP 404) が返されます。

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }
    else
    {
        Movie = movie;
    }
    return Page();
}

コンカレンシー例外処理の確認

OnPostAsync メソッドを Pages/Movies/Edit.cshtml.cs ファイルで確認します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.Id == id);
}

上のコードでは、一方のクライアントがムービーを削除し、もう一方のクライアントがムービーに変更を投稿した場合に、コンカレンシーの例外を検出します。

catch ブロックをテストするには:

  1. catch (DbUpdateConcurrencyException) にブレークポイントを設定する。
  2. ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
  3. 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
  4. 前のブラウザー ウィンドウで、ムービーに変更を投稿します。

実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、同時実行の競合を処理するを参照してください。

レビューの投稿とバインディング

Pages/Movies/Edit.cshtml.cs ファイルを調べます。

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.Id == id);
    }

Movies/Edit ページへの HTTP GET 要求が行われた場合 (例: https://localhost:5001/Movies/Edit/3):

  • OnGetAsync メソッドはデータベースからムービーをフェッチし、Page メソッドを返します。
  • Page メソッドを使用すると、Pages/Movies/Edit.cshtmlRazor ページがレンダリングされます。 Pages/Movies/Edit.cshtml ファイルにはモデル ディレクティブ @model RazorPagesMovie.Pages.Movies.EditModel が含まれています。これにより、ページで movie モデルが使用できるようになります。
  • 編集フォームには、ムービーからの値が表示されます。

Movies/Edit ページが投稿された場合:

  • ページのフォーム値は Movie プロパティにバインドされます。 [BindProperty] 属性により、モデル バインドが有効になります。

    [BindProperty]
    public Movie Movie { get; set; }
    
  • モデルの状態にエラーがある (たとえば、ReleaseDate を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。

  • モデル エラーがない場合、ムービーは保存されます。

[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] OnPostAsync ページの HTTP POST Razor メソッドも [編集] OnPostAsync ページの Razor メソッドと同様のパターンに従います。

次のステップ

スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は Release Date のように 2語で表記する必要があります。

Chrome で開かれているムービー アプリケーション

モデルを更新する

次の強調表示されているコードを使用して、Models/Movie.cs を更新します。

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

前のコードでは:

  • [Column(TypeName = "decimal(18, 2)")] データ注釈により、Entity Framework Core で Price をデータベース内の通貨に正しくマップできるようになります。 詳細については、「Data Types」(データ型) を参照してください。
  • [Display] 属性では、フィールドの表示名を指定します。 上記のコードでは、Release Date ではなく ReleaseDate です。
  • [DataType] 属性では、データの型 (Date) を指定します。 フィールドに格納されている時刻情報は表示されません。

DataAnnotations については、次のチュートリアルで説明します。

Pages/Movies に移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:1234/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、および [削除] の各リンクは、Pages/Movies/Index.cshtml ファイル内の Anchor Tag Helper によって生成されます。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーによって、HTML href 属性値が Razor ページ (ルートは相対)、asp-page、およびルート ID (asp-route-id) から動的に生成されます。 詳細については、「ページの URL の生成」をご覧ください。

ブラウザーから [ソースの表示] を使用して、生成されたマークアップを確認します。 生成された HTML の部分を以下に示します。

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動的に生成されたリンクでは、クエリ文字列でムービー ID が渡されます。 たとえば、https://localhost:5001/Movies/Details?id=1 の ?id=1 です。

ルート テンプレートの追加

{id:int} ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の Razor ページを更新します。 これらの各ページのページ ディレクティブを @page から @page "{id:int}" に変更します。 アプリを実行してから、ソースを表示します。

生成される HTML では、次のように URL のパス部分に ID を追加します。

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

{id:int} ルート テンプレートを持つページへの、整数を含まない要求では、HTTP 404 (見つかりません) エラーが返されます。 たとえば、 https://localhost:5001/Movies/Details は 404 エラーを返します。 ID を省略可能にするには、ルート制約に ? を追加します。

@page "{id:int?}"

@page "{id:int?}" の動作をテストします:

  1. Pages/Movies/Details.cshtml のページ ディレクティブを @page "{id:int?}" に設定してください。
  2. public async Task<IActionResult> OnGetAsync(int? id) のブレーク ポイントを、Pages/Movies/Details.cshtml.cs で設定します。
  3. https://localhost:5001/Movies/Details/ に移動します。

@page "{id:int}" ディレクティブでは、ブレークポイントがヒットすることはありません。 ルーティング エンジンは、HTTP 404 を返します。 @page "{id:int?}" を使用すると、OnGetAsync メソッドから NotFound (HTTP 404) が返されます。

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

コンカレンシー例外処理の確認

OnPostAsync メソッドを Pages/Movies/Edit.cshtml.cs ファイルで確認します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

上のコードでは、一方のクライアントがムービーを削除し、もう一方のクライアントがムービーに変更を投稿した場合に、コンカレンシーの例外を検出します。

catch ブロックをテストするには:

  1. catch (DbUpdateConcurrencyException) にブレークポイントを設定する。
  2. ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
  3. 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
  4. 前のブラウザー ウィンドウで、ムービーに変更を投稿します。

実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、同時実行の競合を処理するを参照してください。

レビューの投稿とバインディング

Pages/Movies/Edit.cshtml.cs ファイルを調べます。

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Movies/Edit ページへの HTTP GET 要求が行われた場合 (例: https://localhost:5001/Movies/Edit/3):

  • OnGetAsync メソッドはデータベースからムービーをフェッチし、Page メソッドを返します。
  • Page メソッドを使用すると、Pages/Movies/Edit.cshtmlRazor ページがレンダリングされます。 Pages/Movies/Edit.cshtml ファイルにはモデル ディレクティブ @model RazorPagesMovie.Pages.Movies.EditModel が含まれています。これにより、ページで movie モデルが使用できるようになります。
  • 編集フォームには、ムービーからの値が表示されます。

Movies/Edit ページが投稿された場合:

  • ページのフォーム値は Movie プロパティにバインドされます。 [BindProperty] 属性により、モデル バインドが有効になります。

    [BindProperty]
    public Movie Movie { get; set; }
    
  • モデルの状態にエラーがある (たとえば、ReleaseDate を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。

  • モデル エラーがない場合、ムービーは保存されます。

[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] OnPostAsync ページの HTTP POST Razor メソッドも [編集] OnPostAsync ページの Razor メソッドと同様のパターンに従います。

次のステップ

スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は Release Date のように 2語で表記する必要があります。

Chrome で開かれているムービー アプリケーション

モデルを更新する

次の強調表示されているコードを使用して、Models/Movie.cs を更新します。

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; } = string.Empty;

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

前のコードでは:

  • [Column(TypeName = "decimal(18, 2)")] データ注釈により、Entity Framework Core で Price をデータベース内の通貨に正しくマップできるようになります。 詳細については、「Data Types」(データ型) を参照してください。
  • [Display] 属性では、フィールドの表示名を指定します。 上記のコードでは、Release Date ではなく ReleaseDate です。
  • [DataType] 属性では、データの型 (Date) を指定します。 フィールドに格納されている時刻情報は表示されません。

DataAnnotations については、次のチュートリアルで説明します。

Pages/Movies に移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:1234/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、および [削除] の各リンクは、Pages/Movies/Index.cshtml ファイル内の Anchor Tag Helper によって生成されます。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーによって、HTML href 属性値が Razor ページ (ルートは相対)、asp-page、およびルート ID (asp-route-id) から動的に生成されます。 詳細については、「ページの URL の生成」をご覧ください。

ブラウザーから [ソースの表示] を使用して、生成されたマークアップを確認します。 生成された HTML の部分を以下に示します。

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動的に生成されたリンクでは、クエリ文字列でムービー ID が渡されます。 たとえば、https://localhost:5001/Movies/Details?id=1 の ?id=1 です。

ルート テンプレートの追加

{id:int} ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の Razor ページを更新します。 これらの各ページのページ ディレクティブを @page から @page "{id:int}" に変更します。 アプリを実行してから、ソースを表示します。

生成される HTML では、次のように URL のパス部分に ID を追加します。

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

{id:int} ルート テンプレートを持つページへの、整数を含まない要求では、HTTP 404 (見つかりません) エラーが返されます。 たとえば、 https://localhost:5001/Movies/Details は 404 エラーを返します。 ID を省略可能にするには、ルート制約に ? を追加します。

@page "{id:int?}"

@page "{id:int?}" の動作をテストします:

  1. Pages/Movies/Details.cshtml のページ ディレクティブを @page "{id:int?}" に設定してください。
  2. public async Task<IActionResult> OnGetAsync(int? id) のブレーク ポイントを、Pages/Movies/Details.cshtml.cs で設定します。
  3. https://localhost:5001/Movies/Details/ に移動します。

@page "{id:int}" ディレクティブでは、ブレークポイントがヒットすることはありません。 ルーティング エンジンは、HTTP 404 を返します。 @page "{id:int?}" を使用すると、OnGetAsync メソッドから NotFound (HTTP 404) が返されます。

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

コンカレンシー例外処理の確認

OnPostAsync メソッドを Pages/Movies/Edit.cshtml.cs ファイルで確認します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.Id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return _context.Movie.Any(e => e.Id == id);
}

上のコードでは、一方のクライアントがムービーを削除し、もう一方のクライアントがムービーに変更を投稿した場合に、コンカレンシーの例外を検出します。

catch ブロックをテストするには:

  1. catch (DbUpdateConcurrencyException) にブレークポイントを設定する。
  2. ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
  3. 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
  4. 前のブラウザー ウィンドウで、ムービーに変更を投稿します。

実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、同時実行の競合を処理するを参照してください。

レビューの投稿とバインディング

Pages/Movies/Edit.cshtml.cs ファイルを調べます。

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.Id == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return _context.Movie.Any(e => e.Id == id);
    }

Movies/Edit ページへの HTTP GET 要求が行われた場合 (例: https://localhost:5001/Movies/Edit/3):

  • OnGetAsync メソッドはデータベースからムービーをフェッチし、Page メソッドを返します。
  • Page メソッドを使用すると、Pages/Movies/Edit.cshtmlRazor ページがレンダリングされます。 Pages/Movies/Edit.cshtml ファイルにはモデル ディレクティブ @model RazorPagesMovie.Pages.Movies.EditModel が含まれています。これにより、ページで movie モデルが使用できるようになります。
  • 編集フォームには、ムービーからの値が表示されます。

Movies/Edit ページが投稿された場合:

  • ページのフォーム値は Movie プロパティにバインドされます。 [BindProperty] 属性により、モデル バインドが有効になります。

    [BindProperty]
    public Movie Movie { get; set; }
    
  • モデルの状態にエラーがある (たとえば、ReleaseDate を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。

  • モデル エラーがない場合、ムービーは保存されます。

[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] OnPostAsync ページの HTTP POST Razor メソッドも [編集] OnPostAsync ページの Razor メソッドと同様のパターンに従います。

次のステップ

スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は Release Date のように 2語で表記する必要があります。

Chrome で開かれているムービー アプリケーション

生成されたコードを更新

次の強調表示されているコードを使用して、Models/Movie.cs を更新します。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; } = string.Empty;

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; } = string.Empty;

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

前のコードでは:

  • [Column(TypeName = "decimal(18, 2)")] データ注釈により、Entity Framework Core で Price をデータベース内の通貨に正しくマップできるようになります。 詳細については、「Data Types」(データ型) を参照してください。
  • [Display] 属性では、フィールドの表示名を指定します。 上のコードでは、"ReleaseDate" ではなく、"Release Date" を指定しています。
  • [DataType] 属性では、データの型 (Date) を指定します。 フィールドに格納されている時刻情報は表示されません。

DataAnnotations については、次のチュートリアルで説明します。

Pages/Movies に移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:1234/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、および [削除] の各リンクは、Pages/Movies/Index.cshtml ファイル内の Anchor Tag Helper によって生成されます。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーによって、HTML href 属性値が Razor ページ (ルートは相対)、asp-page、およびルート ID (asp-route-id) から動的に生成されます。 詳細については、「ページの URL の生成」をご覧ください。

ブラウザーから [ソースの表示] を使用して、生成されたマークアップを確認します。 生成された HTML の部分を以下に示します。

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動的に生成されたリンクでは、クエリ文字列でムービー ID が渡されます。 たとえば、https://localhost:5001/Movies/Details?id=1 の ?id=1 です。

ルート テンプレートの追加

{id:int} ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の Razor ページを更新します。 これらの各ページのページ ディレクティブを @page から @page "{id:int}" に変更します。 アプリを実行してから、ソースを表示します。

生成される HTML では、次のように URL のパス部分に ID を追加します。

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

整数を含まない、{id:int} ルート テンプレートを使用するページへの要求では、HTTP 404 (見つかりません) エラーが返されます。 たとえば、https://localhost:5001/Movies/Details の場合は 404 エラーが返されます。 ID を省略可能にするには、ルート制約に ? を追加します。

@page "{id:int?}"

@page "{id:int?}" の動作をテストします:

  1. Pages/Movies/Details.cshtml のページ ディレクティブを @page "{id:int?}" に設定してください。
  2. public async Task<IActionResult> OnGetAsync(int? id) のブレーク ポイントを、Pages/Movies/Details.cshtml.cs で設定します。
  3. https://localhost:5001/Movies/Details/ に移動します。

@page "{id:int}" ディレクティブでは、ブレークポイントがヒットすることはありません。 ルーティング エンジンは、HTTP 404 を返します。 @page "{id:int?}" を使用すると、OnGetAsync メソッドから NotFound (HTTP 404) が返されます。

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

コンカレンシー例外処理の確認

OnPostAsync メソッドを Pages/Movies/Edit.cshtml.cs ファイルで確認します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
  return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}

上のコードでは、一方のクライアントがムービーを削除し、もう一方のクライアントがムービーに変更を投稿した場合に、コンカレンシーの例外を検出します。 2 つ以上のクライアントで同じムービーを同時に編集しているために発生する競合は、前のコードでは検出されません。 この場合、複数のクライアントによる編集は SaveChanges が呼び出される順番で適用され、後で適用される編集は、古い値で以前の編集を上書きすることがあります。

catch ブロックをテストするには:

  1. catch (DbUpdateConcurrencyException) にブレークポイントを設定する。
  2. ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
  3. 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
  4. 前のブラウザー ウィンドウで、ムービーに変更を投稿します。

製品版コードでは、複数のクライアントが同時に 1 つのエンティティを編集するなど、付加的なコンカレンシーの競合を検出したい場合があります。 詳細については、同時実行の競合を処理するを参照してください。

レビューの投稿とバインディング

Pages/Movies/Edit.cshtml.cs ファイルを調べます。

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; } = default!;

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null || _context.Movie == null)
        {
            return NotFound();
        }

        var movie =  await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
        if (movie == null)
        {
            return NotFound();
        }
        Movie = movie;
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
      return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
    }

Movies/Edit ページへの HTTP GET 要求が行われた場合 (例: https://localhost:5001/Movies/Edit/3):

  • OnGetAsync メソッドはデータベースからムービーをフェッチし、Page メソッドを返します。
  • Page メソッドを使用すると、Pages/Movies/Edit.cshtmlRazor ページがレンダリングされます。 Pages/Movies/Edit.cshtml ファイルにはモデル ディレクティブ @model RazorPagesMovie.Pages.Movies.EditModel が含まれています。これにより、ページで movie モデルが使用できるようになります。
  • 編集フォームには、ムービーからの値が表示されます。

Movies/Edit ページが投稿された場合:

  • ページのフォーム値は Movie プロパティにバインドされます。 [BindProperty] 属性により、モデル バインドが有効になります。

    [BindProperty]
    public Movie Movie { get; set; }
    
  • モデルの状態にエラーがある (たとえば、ReleaseDate を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。

  • モデル エラーがない場合、ムービーは保存されます。

[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] OnPostAsync ページの HTTP POST Razor メソッドも [編集] OnPostAsync ページの Razor メソッドと同様のパターンに従います。

次のステップ

スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は Release Date のように 2語で表記する必要があります。

Chrome で開かれているムービー アプリケーション

生成されたコードを更新

Models/Movie.cs ファイルを開き、下のコードで強調表示されている行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

前のコードでは:

  • [Column(TypeName = "decimal(18, 2)")] データ注釈により、Entity Framework Core で Price をデータベース内の通貨に正しくマップできるようになります。 詳細については、「Data Types」(データ型) を参照してください。
  • [Display] 属性では、フィールドの表示名を指定します。 上のコードでは、"ReleaseDate" ではなく、"Release Date" を指定しています。
  • [DataType] 属性では、データの型 (Date) を指定します。 フィールドに格納されている時刻情報は表示されません。

DataAnnotations については、次のチュートリアルで説明します。

Pages/Movies に移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:1234/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、および [削除] の各リンクは、Pages/Movies/Index.cshtml ファイル内の Anchor Tag Helper によって生成されます。

@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーによって、HTML href 属性値が Razor ページ (ルートは相対)、asp-page、およびルート ID (asp-route-id) から動的に生成されます。 詳細については、「ページの URL の生成」をご覧ください。

ブラウザーから [ソースの表示] を使用して、生成されたマークアップを確認します。 生成された HTML の部分を以下に示します。

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

動的に生成されたリンクでは、クエリ文字列でムービー ID が渡されます。 たとえば、https://localhost:5001/Movies/Details?id=1 の ?id=1 です。

ルート テンプレートの追加

{id:int} ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の Razor ページを更新します。 これらの各ページのページ ディレクティブを @page から @page "{id:int}" に変更します。 アプリを実行してから、ソースを表示します。

生成される HTML では、次のように URL のパス部分に ID を追加します。

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

整数を含まない、{id:int} ルート テンプレートを使用するページへの要求では、HTTP 404 (見つかりません) エラーが返されます。 たとえば、https://localhost:5001/Movies/Details の場合は 404 エラーが返されます。 ID を省略可能にするには、ルート制約に ? を追加します。

@page "{id:int?}"

@page "{id:int?}" の動作をテストします:

  1. Pages/Movies/Details.cshtml のページ ディレクティブを @page "{id:int?}" に設定してください。
  2. public async Task<IActionResult> OnGetAsync(int? id) のブレーク ポイントを、Pages/Movies/Details.cshtml.cs で設定します。
  3. https://localhost:5001/Movies/Details/ に移動します。

@page "{id:int}" ディレクティブでは、ブレークポイントがヒットすることはありません。 ルーティング エンジンは、HTTP 404 を返します。 @page "{id:int?}" を使用すると、OnGetAsync メソッドから NotFound (HTTP 404) が返されます。

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

    if (Movie == null)
    {
        return NotFound();
    }
    return Page();
}

コンカレンシー例外処理の確認

OnPostAsync メソッドを Pages/Movies/Edit.cshtml.cs ファイルで確認します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Attach(Movie).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!MovieExists(Movie.ID))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return RedirectToPage("./Index");
}

private bool MovieExists(int id)
{
    return _context.Movie.Any(e => e.ID == id);
}

上のコードでは、一方のクライアントがムービーを削除し、もう一方のクライアントがムービーに変更を投稿した場合に、コンカレンシーの例外を検出します。

catch ブロックをテストするには:

  1. catch (DbUpdateConcurrencyException) にブレークポイントを設定する。
  2. ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
  3. 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
  4. 前のブラウザー ウィンドウで、ムービーに変更を投稿します。

実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、同時実行の競合を処理するを参照してください。

レビューの投稿とバインディング

Pages/Movies/Edit.cshtml.cs ファイルを調べます。

public class EditModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Movie Movie { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

        if (Movie == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool MovieExists(int id)
    {
        return _context.Movie.Any(e => e.ID == id);
    }

Movies/Edit ページへの HTTP GET 要求が行われた場合 (例: https://localhost:5001/Movies/Edit/3):

  • OnGetAsync メソッドはデータベースからムービーをフェッチし、Page メソッドを返します。
  • Page メソッドを使用すると、Pages/Movies/Edit.cshtmlRazor ページがレンダリングされます。 Pages/Movies/Edit.cshtml ファイルにはモデル ディレクティブ @model RazorPagesMovie.Pages.Movies.EditModel が含まれています。これにより、ページで movie モデルが使用できるようになります。
  • 編集フォームには、ムービーからの値が表示されます。

Movies/Edit ページが投稿された場合:

  • ページのフォーム値は Movie プロパティにバインドされます。 [BindProperty] 属性により、モデル バインドが有効になります。

    [BindProperty]
    public Movie Movie { get; set; }
    
  • モデルの状態にエラーがある (たとえば、ReleaseDate を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。

  • モデル エラーがない場合、ムービーは保存されます。

[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] OnPostAsync ページの HTTP POST Razor メソッドも [編集] OnPostAsync ページの Razor メソッドと同様のパターンに従います。

次のステップ