noxi雑記

.NET、Angularまわりの小ネタブログ

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を使用していますが、WindowsVisual 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アプリのトップディレクトリとなり、JavaScriptCSS、画像などはここに格納します。

作成後に実行してみるとこんな画面が表示されます。実行するには画面右上の実行ボタンを押します。実行すると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)' から ControllerContactController という名前で追加します。

生成されたコントローラーの中身
生成されたコントローラーの中身

次に画像上の赤くなっている return View()View を選択し、 'Alt + Enter' するとViewの作成オプションが表示されるので、 Create partial view 'Index' を選択します。選択すると Views/Contact/Index.cshtml が生成されます。

View作成オプション
View作成オプション

生成された 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-controllerasp-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-controllerasp-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の属性の生成、モデルクラスから値のバインドが実行されます。

生成されたフォームの見た目
生成されたフォームの見た目

生成されたフォームのHTML
生成されたフォームのHTML

ラベルの名称を変更するにはモデルクラスのプロパティに [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-summaryasp-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.NETASP.NET CoreではMVCの特にVの部分が大分変わったと聞いています。筆者はASP.NETMVC開発をした経験が無いので差分はよく分からないのですが、HTMLの属性としてヘルパーを書けるのはHTML構造を大きく変えること無く組み込みができるためとてもべんりだと思います。バリデーションのエラーメッセージだけはさすがにどうにかならないものか、、