ASP.NET Core MVCの公式チュートリアルを丁寧に解説する記事の第4回(最終回)です。
第3回では、タグヘルパーやPOSTリクエストの処理について確認し、最後に検索機能の追加を行いました。
最終回となる第4回では、前半でプロパティ(列)の追加を行い、後半では検証属性の追加を行っていきます。
公式チュートリアルのパート8~10に対応しています。
Movieモデルに新しいプロパティの追加
まずはMovie.cs
に新たにRating(レイティング)というプロパティ(列)を追加してみましょう。
ちなみにレイティングとは↓のような分類のことです。
また、それぞれのプロパティ名が日本語で画面に表示されるようにDisplayName属性を追加します。
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
[DisplayName("タイトル")]
public string? Title { get; set; }
[DisplayName("ジャンル")]
public string? Genre { get; set; }
[DisplayName("興行収入(億円)")]
public decimal Revenue { get; set; }
[DisplayName("公開日")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DisplayName("レイティング")]
public string? Rating { get; set; }
}
}
新たなプロパティを追加したので、まずはMoviesControllerのCreate
とEdit
メソッドのBind属性を修正します。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Title,Genre,Revenue,ReleaseDate,Rating")] Movie movie)
<略>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,Genre,Revenue,ReleaseDate,Rating")] Movie movie)
次にViews/Movies/index.cshtml
にRatingプロパティを追加します。
<略>
<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].Revenue)
</th>
<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.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Revenue)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
その他のテンプレートファイルにも同様にRatingを追加していきます。
Create.cshtml
<略>
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Revenue" class="control-label"></label>
<input asp-for="Revenue" class="form-control" />
<span asp-validation-for="Revenue" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Rating" class="control-label"></label>
<input asp-for="Rating" class="form-control" />
<span asp-validation-for="Rating" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<略>
Delete.cshtml
<略>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Revenue)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Revenue)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Rating)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Rating)
</dd>
</dl>
<略>
Details.cshtml
<略>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Revenue)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Revenue)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Rating)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Rating)
</dd>
</dl>
</div>
<略>
Edit.cshtml
<略>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Revenue" class="control-label"></label>
<input asp-for="Revenue" class="form-control" />
<span asp-validation-for="Revenue" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Rating" class="control-label"></label>
<input asp-for="Rating" class="form-control" />
<span asp-validation-for="Rating" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<略>
以上でプロパティ追加に伴う修正は完了です。
コードファーストマイグレーションでテーブルを実行
このままアプリをデバッグ実行すると、「’Rating’という名前の列がテーブルに存在しない」というエラーが出てしまいます。
解決するためには、既存のテーブル構造をMovieモデルの構造に合わせる必要があります。
いくつか方法はありますが、今回は既に登録済みのデータを削除したくないため、コードファーストマイグレーション(Code First Migrations)という手法でテーブルに新たな列の追加を行います。
コードファーストマイグレーションとは、コード(モデル)の構造に合わせて実際のテーブル構造を作成したり変更ができる手法です。
通常であればCreate Table文を実行してテーブルを作成する必要がありますが、コードファーストを使えばC#のコードを書くだけで自動でテーブルを作成してくれます。
では早速実行してみましょう。※第2回で実行したマイグレーションと同じ手順です。
「ツール」→「NuGetパッケージマネージャー」→「パッケージマネージャーコンソール」でコンソールを開き、以下のコマンドを実行します。
Add-Migration Rating
Update-Database
※「Rating」という名前は任意です。変更内容が一目でわかるような名前を付けるといいでしょう。
実行すると「Migrations」フォルダ内に「<日付と時刻>_Rating.cs」というファイルが作成され、テーブルに「Rating」という列が追加されます。
「SQL Server オブジェクトエクスプローラー」からMovieテーブルを開いて確認してみましょう。
なお、このままではRating列がすべてNULLになっていてデバッグ実行するとエラーになってしまうので、Rating列に以下のように適当な値を入力しておきます。
ここまでできたら、デバッグ実行してみてください。
「レイティング」列が表示されていれば成功です。
検証属性の追加
ここからは、Movieモデルに新しい検証属性を追加します。以下のように書き換えてください。
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
[DisplayName("タイトル")]
[StringLength(60, MinimumLength = 3)]
[Required]
public string? Title { get; set; }
[DisplayName("ジャンル")]
[StringLength(30)]
[Required]
public string? Genre { get; set; }
[DisplayName("興行収入(億円)")]
[Range(1, 1000)]
public decimal Revenue { get; set; }
[DisplayName("公開日")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DisplayName("レイティング")]
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string? Rating { get; set; }
}
}
それぞれの検証属性について簡単に説明します。
[Required]
:入力必須。空白はNG。[StringLength]
:文字数の上限を指定。MinimumLength
で最小文字数を指定可能。[Range]
:値の範囲を指定。[Range(1, 1000)]
の場合は1~1000の値が入力可能。[RegularExpression]
:正規表現を指定。正規表現についての解説は省きますが、「レイティング」では「最初の文字が大文字でその後は半角英数字+記号が使用できる」という意味になります。[DataType]
:厳密には検証属性ではなく書式属性で、データベースの型よりも具体的なデータ型を指定するために使用します。「公開日」はデータベースでは日付の他に時間の値を持っていますが、画面に表示するのは日付のみのためDateを指定しています。
ここで紹介したのは一部で、この他にも多くの検証属性が用意されています。詳細は下記ドキュメントを参照してください。
System.ComponentModel.DataAnnotations 名前空間
ちなみに検証属性は以下のように1行で記述することもできますが、可読性はあまりよくないので分けて書いた方がいいでしょう。
[DisplayName("ジャンル"), StringLength(30), Required]
public string Genre { get; set; }
それではアプリを実行して「新規作成」からCreate画面を開き、わざとエラーになるような入力をして検証属性の動作を確認してみましょう。
正しく検証できていることが確認できました。これらはブラウザ(jQuery)で検証が行われ、エラーがなくなるまでサーバ側にデータを送信することができません。
Modelで検証属性を指定すれば、ControllerやView(Create.cshtml
など)を変更しなくてもすべての画面で検証が適用できる点が便利です。
今回であれば、Create NewだけでなくEdit画面でも同様の検証が適用されています。
なお、エラーメッセージを日本語化したい場合は次のようにErrorMessage
プロパティを指定すればOKです。
[DisplayName("タイトル")]
[StringLength(60, MinimumLength = 3, ErrorMessage = "3文字以上、60文字以内で入力してください。")]
[Required(ErrorMessage = "入力必須です。")]
public string Title { get; set; }
おわりに
前半ではプロパティの追加とコードファーストマイグレーションによるDBの更新、後半では検証属性の追加について確認しました。
以上で、全4回のASP․NET Core MVCのチュートリアルを丁寧に解説するシリーズは終了となります。
最後までお疲れさまでした。
今後さらに理解を深めたい方に向けて、おすすめ書籍や当ブログの記事をご紹介します。
Azure上にデプロイしてアプリを公開したい
AzureのApp Serviceというサービスを使えば、作成したASP.NET Coreアプリをお手軽に公開することができます。
下記記事ではアプリをデプロイする手順を解説しています。
ASP.NET Core の基本的な仕組みを知りたい
下記記事では、Program.cs
に記載されている内容を中心に、ASP.NET Core の裏側の動きについて解説しています。
ASP.NET Core MVC以外のフレームワークも学んでみたい
これからASP.NET Coreで本格的にアプリケーションを作るなら、近年需要が高まっており、Microsoftが開発に力を入れているBlazorを学ぶのもおすすめです。
下記記事では、BlazorでCRUD機能を持つアプリを作る方法を解説しています。