記事目次
- ASP.NET Core 3.0 Razor Pages 事始め(1) - はじめてのRazor Pagesアプリケーション
- ASP.NET Core 3.0 Razor Pages 事始め(2) - スキャフォールディングとDBマイグレーション
- ASP.NET Core 3.0 Razor Pages 事始め(3) - マイグレーションのやり直しとURLルーティング
- ASP.NET Core 3.0 Razor Pages 事始め(4) - ページモデルとページハンドラ
- ASP.NET Core 3.0 Razor Pages 事始め(5) - Postページハンドラとタグヘルパー <-- この記事
- ASP.NET Core 3.0 Razor Pages 事始め(6) - データベースに初期値を設定する
- ASP.NET Core 3.0 Razor Pages 事始め(7) - Viewの変更とコンカレンシー例外処理
- ASP.NET Core 3.0 Razor Pages 事始め(8) - 検索機能の追加
- ASP.NET Core 3.0 Razor Pages 事始め(9) - ページに新しいフィールドを追加する
- ASP.NET Core 3.0 Razor Pages 事始め(10) - 検証機能の追加
- 前回ASP.NET Core 3.0 Razor Pages 事始め(3)の続きです。
- ASP.NET Core 3.0 Razor Pages 事始め(4)の続きです。
なかなか先に進まないですが、ただ動かすだけではなく、コードを理解することが大切だと思うので、今回も、スキャフォールディングで作成されたソースコードを見ていきます。
Create.cshtml.cs
このファイルには、以下に示すように CreateModel
クラスが定義されています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages_Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Models.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; }
// To protect from overposting attacks, please 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.Movies.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
OnGetメソッド
Index.cshtmlと違い、同期メソッドとして定義されています。
public IActionResult OnGet()
{
return Page();
}
これは、中で非同期メソッド呼ぶ必要がないからですね。
試しに、
public void OnGet() {
}
と書き換えて実行してみました。想像通り、正しく動作しました。
OnPostAsync メソッド
CreateModel
クラスには、もう一つのメソッドが定義されています。
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movies.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Http Postに対応するメソッドです。
最初のif文は、旧来の ASP.NET MVCと同じですね。
if (!ModelState.IsValid)
{
return Page();
}
これで、モデルの検証結果を判定し、エラーだったら、再度同じページを表示させています。
エラーがなかった場合は、
_context.Movies.Add(Movie);
await _context.SaveChangesAsync();
で入力されたMovieデータを追加し、データベースを更新しています。
BindProperty属性
上記Addメソッド
の引数のMovie
は、自分自身(CreateModelクラス内)に定義されたプロパティです。
[BindProperty]
public Movie Movie { get; set; }
Webページで入力されPOSTされたデータは、このプロパティにバインドされます。
[BindProperty]属性が、バインドされるプロパティであることを示しています。
RedirectToPageメソッド
最後に、
return RedirectToPage("./Index");
で、同一パスの、Indexページにリダイレクト(遷移)させています。
Create.cshtml
Create.cshtmlについて見てみます。
@page
@model RazorPagesMovie.Pages_Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
これを見ると、スキャフォールディングエンジンは、IDプロパティは出力の対象外のようですね。
Create.cshtmlでは、Index.cshtmlでは使われていなかったタグヘルパーがいくつか使われています。
formタグヘルパー
<form method="post">
見た目は、HTMLの<form>
タグそのものですが、自動的に偽造防止トークンが出力されます。
僕の環境だと、</form>
閉じタグの直前に以下の隠しフィールドが挿入されてました。
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Kws0PPAm1NChsmiTNfRcsX11bkfDPchGc83DJyKlltDfES4-RRpQRGC1_kpkDISRPnKaeSbEBVxYPCqSppRFCM1PieCGNkLk_2u8oUEuBeguEpVQISizdTFjnuCRDIu-IGMQEDP7MNq6UJM57TBku8">
label タグヘルパー
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
このヘルパーは、以下のタグを生成します。
<label class="control-label" for="Movie_ReleaseDate">ReleaseDate</label>
asp-for
で、指定したモデルに該当する for
属性と、ラベルのキャプションを生成します。<label>
要素のコンテンツも自動で挿入されている点に注目です。
input タグヘルパー
<input asp-for="Movie.ReleaseDate" class="form-control" />
asp-for
属性で指定したモデル(ここでは、Movie.ReleaseDate
)の型やDataAnnotation
属性の情報を使用して、適切な<input>
要素を出力します。
<input class="form-control" type="date" data-val="true" data-val-required="The ReleaseDate field is required." id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
また、jQueryのクライアント検証に必要な属性も追加します。例えば、data-val-required
は、必須属性であることを示しています。この属性値で設定したメッセージが、後述の検証タグヘルパー(asp-validation-for
)の場所に表示されます。
アンカータグヘルパー
<a asp-page="Index">Back to List</a>
アンカータグを生成するタグヘルパーです。href
ではなく、asp-page=
属性を指定します。ASP.NET Core MVCだと、asp-controller
, asp-action
ですが、Razorページの場合は、asp-page
です。
この場合は、以下のHTMLが出力されます。
<a href="/Movies">Back to List</a>
検証タグヘルパー
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
この2つが、検証タグヘルパーです。asp-validation-summary
, asp-validation-for
属性がそれを表します。検証エラーがあった時に、ここにエラーメッセージが表示されます。
asp-validation-summary
は、検証エラーの概要を表示するために使用されます。
asp-validation-for
は、特定の入力項目に対する検証エラーメッセージを表示するために使用されます。
なお、create.cshtml
の最後にある
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
は、クライアント検証に必要になります。これがないと、jQueryのクライアント検証が有効になりません。
具体的には、_Layout.cshtml
内に以下の記述があるのですが、
@RenderSection("Scripts", required: false)
この@RenderSection
が書かれた場所に、_ValidationScriptsPartial.cshtml
に書かれた内容が、挿入されることで、jQueryクライアント検証が有効になります。
wwwroot フォルダ
jQueryの話題がすこし出てきたので、wwwrootフォルダの中ものぞいてみます。
このフォルダは、以下のようなフォルダ階層になっていました。
wwwroot
├─css
├─js
└─lib
├─bootstrap
│ └─dist
│ ├─css
│ └─js
├─jquery
│ └─dist
├─jquery-validation
│ └─dist
└─jquery-validation-unobtrusive
アプリで作成した css, js ファイルは、それぞれ、css, js フォルダに置きます。libは、外部パッケージを置く場所のようです。
画像ファイルがある場合は、imagesフォルダを作成するのが標準のやり方のようです。
ちなみに、bootstrapのバージョンは v4.3.1 jQuery のバージョンは v3.3.1 でした。
その他のページ
Index, Create の2つのページについて見てきましたが、残りのページの特徴的な部分だけ見てみましょう。
asp-route-id
属性
以下は、Delete.cshtml.csのOnGetAsync
メソッドです。
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movies.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
引数 id を受け取っています。
index.cshtmlの以下のリンクがクリックされた時に、この上記メソッドが呼び出されます。
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
asp-route-id="@item.ID"
で指定した IDの値が、引数idに渡ることになります。
ちなみに、上記のタグヘルパーで、以下のような出力が得られます。
<a href="/Movies/Delete?id=2">Delete</a>
この例では、仮引数idに、2が渡ってくることになります。
return NotFound()
もし、idに該当する行がテーブルになければ、
return NotFound();
で、404がクライアントに返ります。
試しに、無理やり NotFound()を返してみたら、エラーページは返らないようです。以下、safariの開発者ツールで確認した画面です。
たしかに、404が返っています。
でもこれではユーザは何が起こったかわからないので、何らかのカスタマイズが必要そうですね。これは後で調べます。
一通りページ関連のソースコードを見てみたので、次は、チュートリアルの「データベースの操作」に移ります。