【ASP.NET Core 2.0】スキャフォールディング機能で作成したコードを編集・確認する
前回スキャフォールディングで自動生成したコードの確認と編集を行います。
Razor Pages は ASP.NET MVC の Model-View-Contollor とは異なり、 Model-View-ViewModel デザインパターンがとられています。
Xxxxx.cshtml がビュー、Xxxxx.cshtml.cs がビューモデルです。
スキャフォールディングしたコードは以下のように編集しました。
一覧ページ
\Pages\Movies\Index.cshtml
@page @model RazorPagesMovie.Pages.Movies.IndexModel @{ ViewData["Title"] = "Index"; } <h2>Index</h2> <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 Pages のビュースクリプトでは先頭に @page ディレクティブを宣言します。
@model ディレクティブは、ビューが参照するモデルオブジェクトを宣言します。
ASP.NET MVC と同様の @ を使ったコードナゲットやコードブロックに加え、asp-* タグヘルパーが使用できます。
\Pages\Movies\Index.cshtml.cs
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using RazorPagesMovie.Models; namespace RazorPagesMovie.Pages.Movies { public class IndexModel : PageModel { private readonly MovieContext _context; public IndexModel(MovieContext context) => this._context = context; public IList<Movie> Movies { get; set; } public async Task OnGetAsync() => this.Movies = await this._context.Movie.ToListAsync(); } }
ページモデルクラスは Microsoft.AspNetCore.Mvc.RazorPages.PageModel を継承します。
ASP.NET Core のDI コンテナーで自動でコンストラクターの引数にコンテキストクラスが渡されます。
Movies プロパティは ビュースクリプト側で foreach で一覧表示されます。
OnGetAsyncでデータベースからレコードを全て取り出します。
作成ページ
\Pages\Movies\Create.cshtml
@page @model RazorPagesMovie.Pages.Movies.CreateModel @{ ViewData["Title"] = "Create"; } <h2>Create</h2> <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-default" /> </div> </form> </div> </div> <div> <a asp-page="Index">Back to List</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
RenderPartialAsync() メソッドでは _ValidationScriptsPartial.cshtml 部分ビューを描画します。
部分ビューでは以下のようにjQuery検証を読み込んでいます。
\Pages\_ValidationScriptsPartial.cshtml
<environment include="Development"> <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> </environment> <environment exclude="Development"> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js" asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator" crossorigin="anonymous" integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k"> </script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js" asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive" crossorigin="anonymous" integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH"> </script> </environment>
\Pages\Movies\Create.cshtml.cs
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using RazorPagesMovie.Models; namespace RazorPagesMovie.Pages.Movies { public class CreateModel : PageModel { private readonly MovieContext _context; public CreateModel(MovieContext context) => this._context = context; public IActionResult OnGet() => Page(); [BindProperty] public Movie Movie { get; set; } public async Task<IActionResult> OnPostAsync() { if (!this.ModelState.IsValid) { return Page(); } this._context.Movie.Add(this.Movie); await this._context.SaveChangesAsync(); return RedirectToPage("./Index"); } } }
Movie プロパティは BindProperty 属性が付与され、ビュースクリプト側でユーザーが入力した値がバインドされます。
OnPostAsync() メソッドでは ユーザーが入力したユーザーが作成したデータを登録します。
検証に失敗した場合、登録は行われません。
更新ページ
\Pages\Movies\Edit.cshtml
@page @model RazorPagesMovie.Pages.Movies.EditModel @{ ViewData["Title"] = "Edit"; } <h2>Edit</h2> <h4>Movie</h4> <hr /> <div class="row"> <div class="col-md-4"> <form method="post"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="Movie.ID" /> <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="Save" class="btn btn-default" /> </div> </form> </div> </div> <div> <a asp-page="./Index">Back to List</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
\Pages\Movies\Edit.cshtml.cs
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 EditModel : PageModel { private readonly MovieContext _context; public EditModel(MovieContext context) => this._context = context; [BindProperty] public Movie Movie { get; set; } public async Task<IActionResult> OnGetAsync(int? id) { if (id == null) { return NotFound(); } this.Movie = await this._context.Movie.SingleOrDefaultAsync(m => m.ID == id); if (this.Movie == null) { return NotFound(); } return Page(); } public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } this._context.Attach(this.Movie).State = EntityState.Modified; try { await this._context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!MovieExists(this.Movie.ID)) { return NotFound(); } else { throw; } } return RedirectToPage("./Index"); } private bool MovieExists(int id) => this._context.Movie.Any(e => e.ID == id); } }
OnGetAsync() メソッドでは {id} がない場合や {id} の 映画がデータベースに存在しない場合、404NotFoundを返します。
ユーザーの入力値で映画情報を更新する OnPostAsync() では DbUpdateConcurrencyException をキャッチするため楽観的同時実行制御がされています。
詳細画面
\Pages\Movies\Details.cshtml
@page @model RazorPagesMovie.Pages.Movies.DetailsModel @{ ViewData["Title"] = "Details"; } <h2>Details</h2> <div> <h4>Movie</h4> <hr /> <dl class="dl-horizontal"> <dt> @Html.DisplayNameFor(model => model.Movie.Title) </dt> <dd> @Html.DisplayFor(model => model.Movie.Title) </dd> <dt> @Html.DisplayNameFor(model => model.Movie.ReleaseDate) </dt> <dd> @Html.DisplayFor(model => model.Movie.ReleaseDate) </dd> <dt> @Html.DisplayNameFor(model => model.Movie.Genre) </dt> <dd> @Html.DisplayFor(model => model.Movie.Genre) </dd> <dt> @Html.DisplayNameFor(model => model.Movie.Price) </dt> <dd> @Html.DisplayFor(model => model.Movie.Price) </dd> </dl> </div> <div> <a asp-page="./Edit" asp-route-id="@Model.Movie.ID">Edit</a> | <a asp-page="./Index">Back to List</a> </div>
\Pages\Movies\Details.cshtml.cs
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 DetailsModel : PageModel { private readonly MovieContext _context; public DetailsModel(MovieContext context) => this._context = context; public Movie Movie { get; set; } public async Task<IActionResult> OnGetAsync(int? id) { if (id == null) { return NotFound(); } this.Movie = await this._context.Movie.SingleOrDefaultAsync(m => m.ID == id); if (this.Movie == null) { return NotFound(); } return Page(); } } }
削除ページ
\Pages\Movies\Delete.cshtml
@page @model RazorPagesMovie.Pages.Movies.DeleteModel @{ ViewData["Title"] = "Delete"; } <h2>Delete</h2> <h3>Are you sure you want to delete this?</h3> <div> <h4>Movie</h4> <hr /> <dl class="dl-horizontal"> <dt> @Html.DisplayNameFor(model => model.Movie.Title) </dt> <dd> @Html.DisplayFor(model => model.Movie.Title) </dd> <dt> @Html.DisplayNameFor(model => model.Movie.ReleaseDate) </dt> <dd> @Html.DisplayFor(model => model.Movie.ReleaseDate) </dd> <dt> @Html.DisplayNameFor(model => model.Movie.Genre) </dt> <dd> @Html.DisplayFor(model => model.Movie.Genre) </dd> <dt> @Html.DisplayNameFor(model => model.Movie.Price) </dt> <dd> @Html.DisplayFor(model => model.Movie.Price) </dd> </dl> <form method="post"> <input type="hidden" asp-for="Movie.ID" /> <input type="submit" value="Delete" class="btn btn-default" /> | <a asp-page="./Index">Back to List</a> </form> </div>
\Pages\Movies\Delete.cshtml.cs
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 DeleteModel : PageModel { private readonly MovieContext _context; public DeleteModel(MovieContext context) => this._context = context; [BindProperty] public Movie Movie { get; set; } public async Task<IActionResult> OnGetAsync(int? id) { if (id == null) { return NotFound(); } this.Movie = await this._context.Movie.SingleOrDefaultAsync(m => m.ID == id); if (this.Movie == null) { return NotFound(); } return Page(); } public async Task<IActionResult> OnPostAsync(int? id) { if (id == null) { return NotFound(); } this.Movie = await this._context.Movie.FindAsync(id); if (this.Movie != null) { this._context.Movie.Remove(this.Movie); await this._context.SaveChangesAsync(); } return RedirectToPage("./Index"); } } }