ASP.NET Core 3.0 Razor Pages 事始め(9) - ページに新しいフィールドを追加する


## 記事目次

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 事始め(8)](https://qiita.com/gushwell/items/134930aba1d181100547)の続きです。

今回は公式チュートリアルの[ASP.NET Core で Razor ページに新しいフィールドを追加する](https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/razor-pages/new-field?view=aspnetcore-3.0&tabs=visual-studio)に沿って進めていこうと思います。




## モデルへプロパティを追加

それでは、チュートリアルに沿って進めていきます。まず、Movieモデルへ評価を示すプロパティを追加します。

Models/Movie.cs ファイルを開き、Rating プロパティを追加します。

```c#
    [Display(Name = "レイティング")]
    public string Rating { get; set; }
```


Index.cshtmlを開き、Rating フィールドを追加します。

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

... 

<table class="table">
    <thead>
        <tr>

            ... 

            <th>
                @Html.DisplayNameFor(model => model.Movies[0].Rating)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movies) {
        <tr>

            ... 

            <td>
                @Html.DisplayFor(modelItem => item.Rating)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">編集</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">詳細</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">削除</a>
            </td>
        </tr>
        }
    </tbody>
</table>
```


Create.cshtmlにも、Ratingのフィールドを追加します。

```html
<div class="form-group">
    <label asp-for="Movie.Rating" class="control-label"></label>
    <input asp-for="Movie.Rating" class="form-control" />
    <span asp-validation-for="Movie.Rating" class="text-danger"></span>
</div>
```            

同様にして、Edit.cshtml, Delete.cshtml, Details.cshtml にもRatingフィールドを追加します。

ビルドして、実行してみます。

しかし、Indexページを開こうとすると、以下の例外が発生してしまいます。

```
An unhandled exception occurred while processing the request.

SqliteException: SQLite Error 1: 'no such column: m.Rating'.
Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(int rc, sqlite3 db)
```

これは、データベースのMovie テーブにRatingカラムがまだ存在しないためです。

## Code First Migrations

 Code First Migrations を利用して、これを解決します。
 
 
まず、`SeedData`クラスを変更し、Ratingプロパティに値を設定するようにします。

```c#
   context.Movies.AddRange(
        new Movie
        {
            Title = "When Harry Met Sally",
            ReleaseDate = DateTime.Parse("1989-2-12"),
            Genre = "Romantic Comedy",
            Price = 800M,
            Rating = "R",
        },

        new Movie
        {
            Title = "Ghostbusters ",
            ReleaseDate = DateTime.Parse("1984-3-13"),
            Genre = "Comedy",
            Price = 900M,
            Rating = "G",
        },

        new Movie
        {
            Title = "Ghostbusters 2",
            ReleaseDate = DateTime.Parse("1986-2-23"),
            Genre = "Comedy",
            Price =1000M,
            Rating = "G",
        },

        new Movie
        {
            Title = "Rio Bravo",
            ReleaseDate = DateTime.Parse("1959-4-15"),
            Genre = "Western",
            Price = 500M,
            Rating = "NA",
        }
    );
```

次のコマンドを実行します。

```c#
dotnet ef database drop
dotnet ef migrations add addrating
dotnet ef database update
```

最初にDBを削除します。これは、SeedDataでのデータ初期化コードを動かすためです。


以下実行結果の抜粋です。

```bash
$ dotnet ef database drop
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.0.0 initialized 'RazorPagesMovieContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: None
Are you sure you want to drop the database 'main' on server 'MvcMovie.db'? (y/N)
y
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.0.0 initialized 'RazorPagesMovieContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: None
Dropping database 'main'.
Successfully dropped database 'main'.

$ dotnet ef migrations add addrating
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.0.0 initialized 'RazorPagesMovieContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: None
Done. To undo this action, use 'ef migrations remove'

$ dotnet ef database update
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.0.0 initialized 'RazorPagesMovieContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20100]
      Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
      PRAGMA journal_mode = 'wal';
Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
PRAGMA journal_mode = 'wal';
Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "__EFMigrationsHistory" (
    "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
    "ProductVersion" TEXT NOT NULL
);

…

Done.
```

正常に終了しました。

なお、SQLiteの場合、EFはカラムの変更はサポートしていませんでしたが、試した範囲では、
カラムの追加は大丈夫みたいです。dropでDB削除せずに、マイグレーションを行なってみましたが、正常にコマンドが終了しました。

## 実行してみる

それでは、実行してみます。

![スクリーンショット 2019-11-15 20.43.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29415/2e928920-4cc9-293c-7a33-db42229d44e4.png "スクリーンショット 2019-11-15 20.43.09.png")


うまく動作しました。


新規追加、編集、詳細なども動作するかを確認します。

![スクリーンショット 2019-11-15 20.46.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29415/9a7565ea-ff0c-39ee-c1df-7b0874384d59.png "スクリーンショット 2019-11-15 20.46.08.png")



ところで、この "R"ってなんだろう？ 調べたら、`Restricted` の頭文字のようです。

R：17歳未満の子供は同伴する保護者または成人の保護者が必要です。

ということらしいです。G, PG, PG-13, R, NC-17 があるらしいです。

[サンプルコード](https://github.com/aspnet/AspNetCore.Docs/blob/master/aspnetcore/tutorials/razor-pages/razor-pages-start/sample/RazorPagesMovie30/Models/SeedDataRating.cs)で代入されていた `NA`は、評価無しということかな。

## 入力時も小数点以下を表示しないようにする


ここまで書いて気がついたんですが、

```C#
 [DisplayFormat(DataFormatString ="{0:#,0}")]
 public decimal Price { get; set; }
```        

と指定してるのに、編集ページ(Edit.cshtml)では、

![スクリーンショット 2019-11-15 20.59.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29415/8cf93d51-d443-9f6a-d3ec-680e6999a8aa.png "スクリーンショット 2019-11-15 20.59.08.png")


小数部が表示されてます。この小数部を表示しないようにするには、`ApplyFormatInEditMode`プロパティを使えば良いみたいです。


```C#
 [DisplayFormat(DataFormatString ="{0:#,0}", ApplyFormatInEditMode = true)]
 public decimal Price { get; set; }
```        

これで以下のような表示になりました。


![スクリーンショット 2019-11-15 20.55.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29415/522f0916-efa1-4ad3-f880-33a45ae5fc80.png "スクリーンショット 2019-11-15 20.55.28.png")

---

今回はこれでおしまい。いよいよ次回がチュートリアルの最後となります。