次の方法で共有


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

Note

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

警告

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

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

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によって生成されます。

@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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーで動的に href ページから HTML 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 が渡されます。 たとえば、?id=1https://localhost:5001/Movies/Details?id=1 です。

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

Razor ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の {id:int} ページを更新します。 これらの各ページのページ ディレクティブを @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);
    }

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

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

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

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

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

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

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

次のステップ

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

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によって生成されます。

@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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーで動的に href ページから HTML 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 が渡されます。 たとえば、?id=1https://localhost:5001/Movies/Details?id=1 です。

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

Razor ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の {id:int} ページを更新します。 これらの各ページのページ ディレクティブを @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);
    }

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

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

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

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

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

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

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

次のステップ

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

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によって生成されます。

@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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーで動的に href ページから HTML 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 が渡されます。 たとえば、?id=1https://localhost:5001/Movies/Details?id=1 です。

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

Razor ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の {id:int} ページを更新します。 これらの各ページのページ ディレクティブを @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);
    }

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

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

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

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

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

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

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

次のステップ

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

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によって生成されます。

@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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーで動的に href ページから HTML 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 が渡されます。 たとえば、?id=1https://localhost:5001/Movies/Details?id=1 です。

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

Razor ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の {id:int} ページを更新します。 これらの各ページのページ ディレクティブを @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();
    }

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

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

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

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

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

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

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

次のステップ

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

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によって生成されます。

@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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。

上のコードでは、アンカー タグ ヘルパーで動的に href ページから HTML 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 が渡されます。 たとえば、?id=1https://localhost:5001/Movies/Details?id=1 です。

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

Razor ルート テンプレートを使用するには、[編集]、[詳細]、[削除] の {id:int} ページを更新します。 これらの各ページのページ ディレクティブを @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);
    }

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

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

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

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

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

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

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

次のステップ