記事目次
- 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)の続きです。
今回は、前々回スキャフォールディング機能によって作成されたRazorページについて、見ていこうと思います。
Index.cshtml/ Index.cshtml.cs
Movies/Index.cshtml.cs と Movies/Index.cshtmlを見ていきす。
Index.cshtml.cs
まずは、C#のコードから
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages_Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Models.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movies { get;set; }
public async Task OnGetAsync()
{
Movies = await _context.Movies.ToListAsync();
}
}
}
PageModel
クラスを継承したIndexModel
というクラスが定義されています。このクラスをページモデルと言うようです。
誤解を恐れずに言えば、一つのページ(ここでは、Index.cshtml)に特化したControllerクラスといったらよいでしょうか?
さらに、ViewModelクラスの特徴も持ち合わせているようなクラスです。
コンストラクタ
コンストラクタでは、DIを使用して、RazorPagesMovie.Models.RazorPagesMovieContext
のインスタンスが引数に渡されます。
これを、_context フィールドに格納しています。
これで、ページモデルでデータベースにアクセスすることが可能になります。
ページハンドラ
OnGetAsync
メソッドが、ブラウザからのリクエストに対応するメソッドです。MVCでいうActionメソッドのようなものですね。これをページハンドラと呼ぶようです。
OnGetAsync
という名前から、Http Get要求に対応するメソッドだとわかります。
非同期メソッドにしない場合は、単に OnGet
という名前にします。が、あえて、同期にする必要はないですね。
このハンドラーが呼び出されると、EF Core使って、データベースから Movieのリストを取得して、Movies
プロパティに格納しています。
戻り値は、Task
(OnGet
の場合はvoid
)ですが、Task<IActionResult>
やIActionResult
を返すこともできます。
例えば、別のページに遷移させたい場合は
return RedirectToPage("/Moveis/Details");
などと書けます。RedirectToAction
ではなく、RedirectToPage
です。
Index.cshtml
次は、このページモデルに対応する ページ(cshtml)ファイルを確認します。
@page
@model RazorPagesMovie.Pages_Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies) {
<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構文です。
先頭の@page
は、Razorページであることを示しています。
@model RazorPagesMovie.Pages.Movies.IndexModel
は、ページで利用するモデルの型を指定します。ここでは、IndexModel
クラスを指定しています。
あと特徴的なのは、以下のようにDisplayNameFor
を使っている行ですね。
@Html.DisplayNameFor(model => model.Movies[0].Title)
@Html.
で利用できるメソッドをHTMLヘルパーと呼んでいます。DisplayNameFor
はその一つです。ASP.NET MVCと同じですね。
このラムダ式に渡ってくるのが、2行目の@model
で指定した IndexModel
クラスのインスタンスです。
DisplayNameFor
では、通常のC#と違って、値がnullであっても、Moviesの要素が空であっても例外は発生しません。
@foreach
文の中では、
@Html.DisplayFor(modelItem => item.Title)
という行があります。ラムダ式の引き数modeiItem
は、IndexModel
オブジェクトなので、ここでは利用できません。そのため、@foreach
で列挙した item
変数を使って、それぞれの Movieオブジェクトにアクセスしています。かなり違和感がある書き方ですが、まあそういうものだと思うしかないですね。
ViewData プロパティ
@{
ViewData["Title"] = "Index";
}
のViewData
は、ページモデル(具体的には、基底クラスであるPageModel
クラスに定義されているディクショナリプロパティです。
この ViewData["Title"]
の値は、Pages/Shared/_Layout.cshtml
で利用されます。
_Layout.cshtml
次に、Pages/Shared/_Layout.cshtml
ファイルも覗いてみます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
…
先頭部分だけ抜きだしてみました。
この<title>
要素で、先ほどのViewData["Title"]
の値を参照しています。
<title>
要素をすこし変更してみます。
<title>@ViewData["Title"] - RpMovie</title>
他にも、RazorPagesMovie
と記述している個所があるので、RpMovie
に変更します。
タグヘルパー
_Layout.cshtmlには、以下のような<a>
タグがあります。
<a class="navbar-brand" asp-area="" asp-page="/Index">RpMovie</a>
この <a>
タグは ASP.NET Coreで利用できるタグヘルパーです。
実際のHTMLの<a>
要素に似ていますが、次のような属性値が書いてあります。
asp-area="" asp-page="/Index"`
この2つの属性の値から、href
属性が作成されます。
ここでは、以下のようにリンク先を変更します。
<a class="navbar-brand" asp-area="" asp-page="/Movies/Index">RpMovie</a>
これで、ページ上部の RpMovie
リンクをクリックすると、/Movies/index
ページに遷移するようになります。
ここで実行してみます。以下が、ブラウザの開発者ツールで確認したHTMLの該当箇所です。
<a class="navbar-brand" href="/Movies">RpMovie</a>
クリックすると、/Movies/Index.cshtml のページに遷移します。
@RenderBody()
_Layout.cshtmlには、以下の行が存在します。
@RenderBody()
この行が、当該ページ(Index.chtml)に置き換わります。
つまり、全てのページで共通する部分を _Layout.cshtml
に書いておき、ページごとに異なる部分を それぞれのページの cshtmlに記述することで、レイアウトを統一できるというわけです。
Pages/_ViewStart.cshtml ファイル
ちなみに、この _Layout.cshtml
は置き換えが可能です。 Pages/_ViewStart.cshtml
ファイルに、デフォルトのレイアウトページ名が設定されています。
@{
Layout = "_Layout";
}
ここを変更すれば、別のレイアウトページが利用されるようになります。(まあ、ここを変更することはないと思いますが...)
次回は、残りのページ(主にCreate.cshtml/ Create.cshtml.cs) について見ていきます。