パート 5、ASP.NET Core アプリでの生成済みページの更新
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は 2 つの単語 (Release Date) にする必要があります。
モデルを更新する
次の強調表示されているコードを使用して、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] 属性では、フィールドの表示名を指定します。 上記のコードでは、
ReleaseDate
ではなくRelease Date
です。 - [DataType] 属性では、データの型 (
Date
) を指定します。 フィールドに格納されている時刻情報は表示されません。
DataAnnotations については、次のチュートリアルで説明します。
Pages/Movies を参照し、 [編集] リンクをポイントしてターゲット URL を確認します。
[編集]、[詳細]、および [削除] の各リンクは、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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
上のコードでは、アンカー タグ ヘルパーで動的に Razor ページから HTML href
属性値 (ルートは相対)、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?}"
の動作をテストするには:
Pages/Movies/Details.cshtml
のページ ディレクティブを@page "{id:int?}"
に設定します。public async Task<IActionResult> OnGetAsync(int? id)
のブレーク ポイントを、Pages/Movies/Details.cshtml.cs
で設定します。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();
}
コンカレンシーの例外処理の確認
Pages/Movies/Edit.cshtml.cs
ファイルで OnPostAsync
メソッドを確認します。
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
ブロックをテストするには、次の操作を行います。
catch (DbUpdateConcurrencyException)
にブレークポイントを設定します。- ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
- 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
- 前のブラウザー ウィンドウで、ムービーに変更を投稿します。
実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、コンカレンシーの競合の処理に関するページを参照してください。
レビューの投稿とバインディング
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.cshtml
Razor ページがレンダリングされます。Pages/Movies/Edit.cshtml
ファイルにはモデルのディレクティブ (@model RazorPagesMovie.Pages.Movies.EditModel
) が含まれています。これにより、ページでムービー モデルが使用できるようになります。- [編集] フォームには、ムービーからの値が表示されます。
Movies/Edit ページが投稿された場合:
ページのフォーム値は
Movie
プロパティにバインドされます。[BindProperty]
属性により、モデル バインドが有効になります。[BindProperty] public Movie Movie { get; set; }
モデルの状態にエラーがある (たとえば、
ReleaseDate
を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。モデル エラーがない場合、ムービーは保存されます。
[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] Razor ページの HTTP POST OnPostAsync
メソッドも [編集] Razor ページの OnPostAsync
メソッドと同様のパターンに従います。
次のステップ
スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は 2 つの単語 (Release Date) にする必要があります。
モデルを更新する
次の強調表示されているコードを使用して、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] 属性では、フィールドの表示名を指定します。 上記のコードでは、
ReleaseDate
ではなくRelease Date
です。 - [DataType] 属性では、データの型 (
Date
) を指定します。 フィールドに格納されている時刻情報は表示されません。
DataAnnotations については、次のチュートリアルで説明します。
Pages/Movies を参照し、 [編集] リンクをポイントしてターゲット URL を確認します。
[編集]、[詳細]、および [削除] の各リンクは、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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
上のコードでは、アンカー タグ ヘルパーで動的に Razor ページから HTML href
属性値 (ルートは相対)、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?}"
の動作をテストするには:
Pages/Movies/Details.cshtml
のページ ディレクティブを@page "{id:int?}"
に設定します。public async Task<IActionResult> OnGetAsync(int? id)
のブレーク ポイントを、Pages/Movies/Details.cshtml.cs
で設定します。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();
}
コンカレンシーの例外処理の確認
Pages/Movies/Edit.cshtml.cs
ファイルで OnPostAsync
メソッドを確認します。
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
ブロックをテストするには、次の操作を行います。
catch (DbUpdateConcurrencyException)
にブレークポイントを設定します。- ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
- 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
- 前のブラウザー ウィンドウで、ムービーに変更を投稿します。
実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、コンカレンシーの競合の処理に関するページを参照してください。
レビューの投稿とバインディング
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.cshtml
Razor ページがレンダリングされます。Pages/Movies/Edit.cshtml
ファイルにはモデルのディレクティブ (@model RazorPagesMovie.Pages.Movies.EditModel
) が含まれています。これにより、ページでムービー モデルが使用できるようになります。- [編集] フォームには、ムービーからの値が表示されます。
Movies/Edit ページが投稿された場合:
ページのフォーム値は
Movie
プロパティにバインドされます。[BindProperty]
属性により、モデル バインドが有効になります。[BindProperty] public Movie Movie { get; set; }
モデルの状態にエラーがある (たとえば、
ReleaseDate
を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。モデル エラーがない場合、ムービーは保存されます。
[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] Razor ページの HTTP POST OnPostAsync
メソッドも [編集] Razor ページの OnPostAsync
メソッドと同様のパターンに従います。
次のステップ
スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は 2 つの単語 (Release Date) にする必要があります。
モデルを更新する
次の強調表示されているコードを使用して、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] 属性では、フィールドの表示名を指定します。 上記のコードでは、
ReleaseDate
ではなくRelease Date
です。 - [DataType] 属性では、データの型 (
Date
) を指定します。 フィールドに格納されている時刻情報は表示されません。
DataAnnotations については、次のチュートリアルで説明します。
Pages/Movies を参照し、 [編集] リンクをポイントしてターゲット URL を確認します。
[編集]、[詳細]、および [削除] の各リンクは、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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
上のコードでは、アンカー タグ ヘルパーで動的に Razor ページから HTML href
属性値 (ルートは相対)、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?}"
の動作をテストするには:
Pages/Movies/Details.cshtml
のページ ディレクティブを@page "{id:int?}"
に設定します。public async Task<IActionResult> OnGetAsync(int? id)
のブレーク ポイントを、Pages/Movies/Details.cshtml.cs
で設定します。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();
}
コンカレンシーの例外処理の確認
Pages/Movies/Edit.cshtml.cs
ファイルで OnPostAsync
メソッドを確認します。
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
ブロックをテストするには、次の操作を行います。
catch (DbUpdateConcurrencyException)
にブレークポイントを設定します。- ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
- 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
- 前のブラウザー ウィンドウで、ムービーに変更を投稿します。
実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、コンカレンシーの競合の処理に関するページを参照してください。
レビューの投稿とバインディング
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.cshtml
Razor ページがレンダリングされます。Pages/Movies/Edit.cshtml
ファイルにはモデルのディレクティブ (@model RazorPagesMovie.Pages.Movies.EditModel
) が含まれています。これにより、ページでムービー モデルが使用できるようになります。- [編集] フォームには、ムービーからの値が表示されます。
Movies/Edit ページが投稿された場合:
ページのフォーム値は
Movie
プロパティにバインドされます。[BindProperty]
属性により、モデル バインドが有効になります。[BindProperty] public Movie Movie { get; set; }
モデルの状態にエラーがある (たとえば、
ReleaseDate
を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。モデル エラーがない場合、ムービーは保存されます。
[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] Razor ページの HTTP POST OnPostAsync
メソッドも [編集] Razor ページの OnPostAsync
メソッドと同様のパターンに従います。
次のステップ
スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は 2 つの単語 (Release Date) にする必要があります。
生成されたコードの更新
次の強調表示されているコードを使用して、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 を確認します。
[編集]、[詳細]、および [削除] の各リンクは、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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
上のコードでは、アンカー タグ ヘルパーで動的に Razor ページから HTML href
属性値 (ルートは相対)、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?}"
の動作をテストするには:
Pages/Movies/Details.cshtml
のページ ディレクティブを@page "{id:int?}"
に設定します。public async Task<IActionResult> OnGetAsync(int? id)
のブレーク ポイントを、Pages/Movies/Details.cshtml.cs
で設定します。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();
}
コンカレンシーの例外処理の確認
Pages/Movies/Edit.cshtml.cs
ファイルで OnPostAsync
メソッドを確認します。
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
ブロックをテストするには、次の操作を行います。
catch (DbUpdateConcurrencyException)
にブレークポイントを設定します。- ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
- 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
- 前のブラウザー ウィンドウで、ムービーに変更を投稿します。
製品版コードでは、複数のクライアントで 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.cshtml
Razor ページがレンダリングされます。Pages/Movies/Edit.cshtml
ファイルにはモデルのディレクティブ (@model RazorPagesMovie.Pages.Movies.EditModel
) が含まれています。これにより、ページでムービー モデルが使用できるようになります。- [編集] フォームには、ムービーからの値が表示されます。
Movies/Edit ページが投稿された場合:
ページのフォーム値は
Movie
プロパティにバインドされます。[BindProperty]
属性により、モデル バインドが有効になります。[BindProperty] public Movie Movie { get; set; }
モデルの状態にエラーがある (たとえば、
ReleaseDate
を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。モデル エラーがない場合、ムービーは保存されます。
[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] Razor ページの HTTP POST OnPostAsync
メソッドも [編集] Razor ページの OnPostAsync
メソッドと同様のパターンに従います。
次のステップ
スキャフォールディングされたムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。 ReleaseDate は 2 つの単語 (Release Date) にする必要があります。
生成されたコードの更新
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 を確認します。
[編集]、[詳細]、および [削除] の各リンクは、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 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。
上のコードでは、アンカー タグ ヘルパーで動的に Razor ページから HTML href
属性値 (ルートは相対)、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?}"
の動作をテストするには:
Pages/Movies/Details.cshtml
のページ ディレクティブを@page "{id:int?}"
に設定します。public async Task<IActionResult> OnGetAsync(int? id)
のブレーク ポイントを、Pages/Movies/Details.cshtml.cs
で設定します。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();
}
コンカレンシーの例外処理の確認
Pages/Movies/Edit.cshtml.cs
ファイルで OnPostAsync
メソッドを確認します。
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
ブロックをテストするには、次の操作を行います。
catch (DbUpdateConcurrencyException)
にブレークポイントを設定します。- ムービーの [編集] を選択し、変更を行います。ただし、 [保存] はしないでください。
- 別のブラウザー ウィンドウで、同じムービーの [削除] リンクを選択してから、ムービーを削除します。
- 前のブラウザー ウィンドウで、ムービーに変更を投稿します。
実稼働環境のコードが、コンカレンシーの競合を検出する可能性があります。 詳細については、コンカレンシーの競合の処理に関するページを参照してください。
レビューの投稿とバインディング
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.cshtml
Razor ページがレンダリングされます。Pages/Movies/Edit.cshtml
ファイルにはモデルのディレクティブ (@model RazorPagesMovie.Pages.Movies.EditModel
) が含まれています。これにより、ページでムービー モデルが使用できるようになります。- [編集] フォームには、ムービーからの値が表示されます。
Movies/Edit ページが投稿された場合:
ページのフォーム値は
Movie
プロパティにバインドされます。[BindProperty]
属性により、モデル バインドが有効になります。[BindProperty] public Movie Movie { get; set; }
モデルの状態にエラーがある (たとえば、
ReleaseDate
を日付に変換できない) 場合、送信された値を含むフォームが再表示されます。モデル エラーがない場合、ムービーは保存されます。
[インデックス]、[作成]、[削除] Razor ページの HTTP GET メソッドも同様のパターンに従います。 [作成] Razor ページの HTTP POST OnPostAsync
メソッドも [編集] Razor ページの OnPostAsync
メソッドと同様のパターンに従います。
次のステップ
ASP.NET Core