ASP.NET Core 2.1 MVCことはじめ(簡単なフォーム作成)
.NET Core 2.1がリリースされて早数ヶ月。MVCを利用して簡単にフォーム画面の開発をしてみます。
前提条件
この記事は以下の環境で開発しています。
- macOS 10.13.6
- .NET Core 2.1.2 (2.1.302)
- JetBrains Rider 2018.1.3
IDEとしてRiderを使用していますが、WindowsでVisual Studioを利用する場合も記事の中身に大差はありません。
プロジェクトの作成
まずはASP.NET Core MVCのプロジェクトを作成します。 New Solution
ボタンから新規ソリューション作成画面を開き、次の設定で作成します。
- テンプレート:.NET Core - ASP.NET Web Application
- 種別:Web App (Model-View-Controller)
作成するとこんな感じのソリューションが表示されます。 Controllers
はURLとViewのマッピングを行うコントローラーを格納するディレクトリ、 Models
はモデルを格納するディレクトリ、 Views
はビューとなるHTMLを格納するディレクトリです。また wwwroot
はWebアプリのトップディレクトリとなり、JavaScriptやCSS、画像などはここに格納します。
作成後に実行してみるとこんな画面が表示されます。実行するには画面右上の実行ボタンを押します。実行するとHTTPSのエラーページが表示されるかも知れませんが、特に気にせず表示するボタンを押して下さい。
Viewに動的な値を表示する
ViewDataを使用する
ViewData
を使用してコントローラーからViewに対して表示する内容を制御してみます。
Controllers/HomeController.cs
を開き、 Name
メソッドを追加します。
public IActionResult Name() { this.ViewData["Name"] = "にゃーん"; return View(); }
そして Views/Home/Name.cshtml
を追加します。なお追加する際は、追加するディレクトリを選択して '⌘ + N (mac)' または 'Alt + Ins (win)' を押すと便利です。 Razar MVC Partial View
を選択します。
<div>@ViewData["Name"]</div>
Viewの追加実行後に実行して /Home/Name
にアクセスすると画面上に にゃーん
が表示されます。
QueryStringの内容を取得する
次にQueryStringで指定された文字列を表示してみます。 Name
メソッドを次のように修正します。
public IActionResult Name([FromQuery] string name) { this.ViewData["Name"] = name ?? "にゃーん"; return View(); }
修正後に実行すると、 /Home/Name
にアクセスすると にゃーん
が表示され、 /Home/Name?name=わん
にアクセスすると わん
と表示されるのが分かります。
フォームを作成する
コントローラーとViewを追加する
まずはフォームを表示するコントローラーとViewを追加します。 Controllers
ディレクトリを選択して '⌘ + N (mac)' または 'Alt + Ins (win)' から Controller
を ContactController
という名前で追加します。
次に画像上の赤くなっている return View()
の View
を選択し、 'Alt + Enter' するとViewの作成オプションが表示されるので、 Create partial view 'Index'
を選択します。選択すると Views/Contact/Index.cshtml
が生成されます。
生成された Views/Contact/Index.cshtml
を開いて、次のフォームHTMLを入力します。
<form method="post"> <div> <label for="name">名前</label> <input id="name" type="text" name="Name"> </div> <div> <label for="age">年齢</label> <input id="age" type="number" name="Age"> </div> <div> <label for="email">メールアドレス</label> <input id="email" type="email" name="Email"> </div> <div> <input type="submit" value="送信"> </div> </form>
実行して /Contact
にアクセスすると作成したフォームが表示されます。
POST先とバインドするモデルを追加する
このままだとサブミットボタンを押しても値がバインドされないため、まずバインド用のモデルを追加します。 Models/ContactModel.cs
を追加します。
(/Models/ContactModel.cs) namespace WebApplication1.Models { public class ContactModel { public string Name { get; set; } public int? Age { get; set; } public string Email { get; set; }E } }
次にPOST先のメソッドを作成します。 ContactController
を開いて次のように修正します。
using Microsoft.AspNetCore.Mvc; using WebApplication1.Models; namespace WebApplication1.Controllers { public class ContactController : Controller { [HttpGet] public IActionResult Index() { return View(); } [HttpPost] public IActionResult Index(ContactModel model) { return View(model); } } }
最後にViewに対してモデルクラスの型設定を追加します。
(/Views/Contact/Index.cshtml) @model ContactModel <form method="post"> ...
[HttpGet]
や [HttpPost]
を設定することでGET/POSTで使用されるメソッドを明示的に区別します。またPOST時にモデルを受け取れるようにします。実行してサブミットボタンを押すと、デバッグではバインドされていることが確認できます。
HTMLにヘルパー属性を追加する(form)
HTMLにASP.NETのヘルパー属性を追加します。まずはフォームのアクション先を asp-controller
とasp-action
を使用してそれぞれコントローラーとアクションメソッドを明示的に指定します。また、同時に受け取り側のアクションメソッドに [ValidateAntiForgeryToken]
を追加します。
(/Views/Contact/Index.cshtml) <form method="post" asp-controller="Contact" asp-action="Index"> ... </form
(/Controllers/ContactController.cs) [HttpPost] [ValidateAntiForgeryToken] public IActionResult Index(ContactModel model) { return View(model); }
formに asp-controller
と asp-action
を設定するとCSRF対策用のトークンを含んだhiddenなinputが追加されます。 [ValidateAntiForgeryToken]
を受け取り側のアクションメソッドに追加することで手軽にCSRF対策が追加できます。
HTMLにヘルパー属性を追加する(input)
次にinputにヘルパー属性を追加します。 asp-for
でモデルのどのプロパティにバインドするかを指定します。
(/Models/Contact/Index.cshtml) @model ContactModel <form method="post" asp-controller="Contact" asp-action="Index"> <div> <label asp-for="Name"></label> <input asp-for="Name"> </div> <div> <label asp-for="Age"></label> <input asp-for="Age"> </div> <div> <label asp-for="Email"></label> <input asp-for="Email"> </div> <div> <input type="submit" value="送信"> </div> </form>
これを実行するとモデルクラスのプロパティからラベルの名前を生成やinputの属性の生成、モデルクラスから値のバインドが実行されます。
ラベルの名称を変更するにはモデルクラスのプロパティに [Display]
属性を設定します。
(/Models/ContactModel.cs) using System.ComponentModel.DataAnnotations; namespace WebApplication1.Models { public class ContactModel { [Display(Name = "名前")] public string Name { get; set; } [Display(Name = "年齢")] public int? Age { get; set; } [Display(Name = "メールアドレス")] public string Email { get; set; } } }
バリデーションを実装する
ASP.NETではシンプルなモデルバリデーションをモデルクラスのプロパティに属性として設定します。例えば「名前は必須で30文字以内、年齢は必須で0歳から100歳まで、メールアドレスは必須で100文字以内」というモデルバリデーションの実装は次のようになります。
(/Models/ContactModel.cs) using System.ComponentModel.DataAnnotations; namespace WebApplication1.Models { public class ContactModel { [Display(Name = "名前")] [Required] [MaxLength(30)] public string Name { get; set; } [Display(Name = "年齢")] [Required] [Range(minimum: 0, maximum: 100)] public int? Age { get; set; } [Display(Name = "メールアドレス")] [Required] [DataType(DataType.EmailAddress)] [MaxLength(100)] public string Email { get; set; } } }
[DataType]
を設定するとinputのtype属性を指定することができます。プロパティの型が数値系の場合は type="number"
が自動で設定されますが type="email"
は自動で設定されないため、明示的に指定します。
そしてバリデーションエラーを表示するためHTMLに asp-validation-summary
と asp-validation-for
を追加します。
(/Models/Contact/Index.cshtml) @model ContactModel <form method="post" asp-controller="Contact" asp-action="Index"> <div asp-validation-summary="All"></div> <div> <span asp-validation-for="Name"></span> <label asp-for="Name"></label> <input asp-for="Name"> </div> <div> <span asp-validation-for="Age"></span> <label asp-for="Age"></label> <input asp-for="Age"> </div> <div> <span asp-validation-for="Email"></span> <label asp-for="Email"></label> <input asp-for="Email"> </div> <div> <input type="submit" value="送信"> </div> </form>
グローバルばんざい、、、!
バリデーションエラーは標準では全て英語で表示されてしまいます。もう一手間加えます。モデルクラスに設定した各バリデーション属性にバリデーションエラーメッセージを追加します。
(/Models/ContactModel.cs) using System.ComponentModel.DataAnnotations; namespace WebApplication1.Models { public class ContactModel { [Display(Name = "名前")] [Required(ErrorMessage = "{0}を入力してください。")] [MaxLength(30, ErrorMessage = "{0}は{1}文字以内で入力してください。")] public string Name { get; set; } [Display(Name = "年齢")] [Required(ErrorMessage = "{0}を入力してください。")] [Range(minimum: 0, maximum: 100, ErrorMessage = "{0}は{1}から{2}の間で入力してください。")] public int? Age { get; set; } [Display(Name = "メールアドレス")] [Required(ErrorMessage = "{0}を入力してください。")] [DataType(DataType.EmailAddress)] [MaxLength(100, ErrorMessage = "{0}は{1}文字以内で入力してください。")] public string Email { get; set; } } }
サブミット後に完了ページを表示する
最後に、バリデーションまで全て通過した状態でサブミットしたら完了ページを表示してみます。
まずは完了ページのViewを作成します。
(/Models/Contact/Complete.cshtml) @model ContactModel <div> <label asp-for="Name"></label> @Model.Name </div> <div> <label asp-for="Age"></label> @Model.Age 歳 </div> <div> <label asp-for="Email"></label> @Model.Email </div>
次にバリデーション通過後にこの画面を表示する様にコントローラーを修正します。 ModelState.IsValid
をチェックすることでモデルバリデーションを通過しているか違反しているかをチェックし、同じ画面を表示するか、完了画面を表示するか分岐させます。
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Index(ContactModel model) { if (!ModelState.IsValid) { return View(model); } return View("Complete", model); }
終わりに
ASP.NETとASP.NET CoreではMVCの特にVの部分が大分変わったと聞いています。筆者はASP.NETでMVC開発をした経験が無いので差分はよく分からないのですが、HTMLの属性としてヘルパーを書けるのはHTML構造を大きく変えること無く組み込みができるためとてもべんりだと思います。バリデーションのエラーメッセージだけはさすがにどうにかならないものか、、