ASP.NET Core 3.0 Razor Pages 事始め(4) - ページモデルとページハンドラ

## 記事目次

1. [ASP.NET Core 3.0 Razor Pages 事始め(1) - はじめてのRazor Pagesアプリケーション](https://qiita.com/gushwell/items/6d8b801923b639d9ea2e)
2. [ASP.NET Core 3.0 Razor Pages 事始め(2) - スキャフォールディングとDBマイグレーション](https://qiita.com/gushwell/items/7eb76edf7006c0f02be4)
3. [ASP.NET Core 3.0 Razor Pages 事始め(3) - マイグレーションのやり直しとURLルーティング](https://qiita.com/gushwell/items/7ebe70f0d6adfd10ae12)
4. [ASP.NET Core 3.0 Razor Pages 事始め(4) - ページモデルとページハンドラ](https://qiita.com/gushwell/items/1deac26aaed945cc8631) <-- この記事
5. [ASP.NET Core 3.0 Razor Pages 事始め(5) - Postページハンドラとタグヘルパー](https://qiita.com/gushwell/items/daf901d4d76301dadea7)
6. [ASP.NET Core 3.0 Razor Pages 事始め(6) - データベースに初期値を設定する](https://qiita.com/gushwell/items/f7be44e668a6e168fc61)
7. [ASP.NET Core 3.0 Razor Pages 事始め(7) - Viewの変更とコンカレンシー例外処理](https://qiita.com/gushwell/items/913e8e95a0a79949af3c)
8. [ASP.NET Core 3.0 Razor Pages 事始め(8) - 検索機能の追加](https://qiita.com/gushwell/items/134930aba1d181100547)
9. [ASP.NET Core 3.0 Razor Pages 事始め(9) - ページに新しいフィールドを追加する](https://qiita.com/gushwell/items/a330e592f76efda13c42)
10. [ASP.NET Core 3.0 Razor Pages 事始め(10) - 検証機能の追加](https://qiita.com/gushwell/items/12e180ef129ea6a05757)

---
前回[ASP.NET Core 3.0 Razor Pages 事始め(3)](https://qiita.com/gushwell/items/7ebe70f0d6adfd10ae12)の続きです。


今回は、[前々回](https://qiita.com/gushwell/items/7eb76edf7006c0f02be4)スキャフォールディング機能によって作成されたRazorページについて、見ていこうと思います。


# Index.cshtml/ Index.cshtml.cs 

Movies/Index.cshtml.cs と　Movies/Index.cshtmlを見ていきす。

## Index.cshtml.cs 

まずは、C#のコードから

```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` を返すこともできます。

例えば、別のページに遷移させたい場合は

```c#
return RedirectToPage("/Moveis/Details");
```

などと書けます。`RedirectToAction` ではなく、`RedirectToPage` です。

## Index.cshtml

次は、このページモデルに対応する ページ(cshtml)ファイルを確認します。


```html
@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ページであることを示しています。

```html
@model RazorPagesMovie.Pages.Movies.IndexModel
```

は、ページで利用するモデルの型を指定します。ここでは、`IndexModel`クラスを指定しています。



あと特徴的なのは、以下のように`DisplayNameFor`を使っている行ですね。

```html
 @Html.DisplayNameFor(model => model.Movies[0].Title)
```

`@Html.`で利用できるメソッドをHTMLヘルパーと呼んでいます。`DisplayNameFor`はその一つです。ASP.NET MVCと同じですね。

このラムダ式に渡ってくるのが、2行目の`@model`で指定した `IndexModel`クラスのインスタンスです。
`DisplayNameFor` では、通常のC#と違って、値がnullであっても、Moviesの要素が空であっても例外は発生しません。

`@foreach`文の中では、

```html
@Html.DisplayFor(modelItem => item.Title)
```                

という行があります。ラムダ式の引き数`modeiItem`は、`IndexModel`オブジェクトなので、ここでは利用できません。そのため、`@foreach`で列挙した `item`変数を使って、それぞれの Movieオブジェクトにアクセスしています。かなり違和感がある書き方ですが、まあそういうものだと思うしかないですね。


### ViewData プロパティ

```html
@{
    ViewData["Title"] = "Index";
}
```

の`ViewData`は、ページモデル(具体的には、基底クラスである`PageModel`クラスに定義されているディクショナリプロパティです。
この `ViewData["Title"]`の値は、`Pages/Shared/_Layout.cshtml` で利用されます。


## _Layout.cshtml

次に、`Pages/Shared/_Layout.cshtml` ファイルも覗いてみます。

```html
<!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>`要素をすこし変更してみます。

```html
<title>@ViewData["Title"] - RpMovie</title>
```    

他にも、`RazorPagesMovie`と記述している個所があるので、`RpMovie`に変更します。


### タグヘルパー

_Layout.cshtmlには、以下のような`<a>`タグがあります。


```html
<a class="navbar-brand" asp-area="" asp-page="/Index">RpMovie</a>
```

この `<a>`タグは ASP.NET Coreで利用できるタグヘルパーです。

実際のHTMLの`<a>`要素に似ていますが、次のような属性値が書いてあります。

```html
asp-area="" asp-page="/Index"`
```

この２つの属性の値から、`href`属性が作成されます。

ここでは、以下のようにリンク先を変更します。

```html
<a class="navbar-brand" asp-area="" asp-page="/Movies/Index">RpMovie</a>
```

これで、ページ上部の `RpMovie`リンクをクリックすると、`/Movies/index`ページに遷移するようになります。


ここで実行してみます。以下が、ブラウザの開発者ツールで確認したHTMLの該当箇所です。

```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) について見ていきます。