4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ASP.NET Core 3.0 Razor Pages 事始め(5) - Postページハンドラとタグヘルパー

Last updated at Posted at 2019-11-04

記事目次

  1. ASP.NET Core 3.0 Razor Pages 事始め(1) - はじめてのRazor Pagesアプリケーション
  2. ASP.NET Core 3.0 Razor Pages 事始め(2) - スキャフォールディングとDBマイグレーション
  3. ASP.NET Core 3.0 Razor Pages 事始め(3) - マイグレーションのやり直しとURLルーティング
  4. ASP.NET Core 3.0 Razor Pages 事始め(4) - ページモデルとページハンドラ
  5. ASP.NET Core 3.0 Razor Pages 事始め(5) - Postページハンドラとタグヘルパー <-- この記事
  6. ASP.NET Core 3.0 Razor Pages 事始め(6) - データベースに初期値を設定する
  7. ASP.NET Core 3.0 Razor Pages 事始め(7) - Viewの変更とコンカレンシー例外処理
  8. ASP.NET Core 3.0 Razor Pages 事始め(8) - 検索機能の追加
  9. ASP.NET Core 3.0 Razor Pages 事始め(9) - ページに新しいフィールドを追加する
  10. ASP.NET Core 3.0 Razor Pages 事始め(10) - 検証機能の追加
  11. 前回ASP.NET Core 3.0 Razor Pages 事始め(3)の続きです。
  12. 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の開発者ツールで確認した画面です。

スクリーンショット 2019-10-30 21.36.01.png

たしかに、404が返っています。

でもこれではユーザは何が起こったかわからないので、何らかのカスタマイズが必要そうですね。これは後で調べます。

一通りページ関連のソースコードを見てみたので、次は、チュートリアルの「データベースの操作」に移ります。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?