ASP.NET Core × jQuery Validationでエラーを表示する

[ASP.NET Core](https://www.asp.net/core/overview/aspnet-vnext)と[jQuery Validation](https://jqueryvalidation.org/)を組み合わせた忘備録。

環境
Visual Studio 2017(2015でも同じですが…)
☆ASP.NET Core 1.1
★bootstrap 3.3.7
★jQuery 3.2.1
★jQuery Validation 1.14.0
★jQuery Validation Unobtrusive 3.2.6

☆NuGetからインストール(あるいは標準でインストールされているもの)
★Bowerからインストール(あるいは標準でインストールされているもの)

※基本、以下コード例はVisual Studio -> 新規プロジェクト作成 -> 「ASP.NET Core Web アプリケーション(.NET Core)」選択時に自動で生成されるサンプルコード一式をそのまま流用しています。

# エラーを表示する

## コード例
ビューモデル

```csharp:Models/ErrorWebApplication.cs
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace ErrorWebApplication.Models
{
    public class ErrorViewModel
    {
        [DisplayName("入力項目")]
        [Required(ErrorMessage = "入力項目は必須です。")]
        public int InputValue { get; set; }
    }
}
```

コントローラー

```csharp:Controllers/HomeController.cs
using ErrorWebApplication.Models;
using Microsoft.AspNetCore.Mvc;

namespace ErrorWebApplication.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        // POST: Home/Index
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(ErrorViewModel viewModel)
        {
            if (ModelState.IsValid)
            {
                if (viewModel.InputValue < 100)
                {
                    // エラーの場合
                    ModelState.AddModelError(string.Empty, "入力値は100以上の値を指定してください。");
                    ModelState.AddModelError(string.Empty, "ちなみに入力値の範囲チェックはRangeAttributeが使えますよ。");
                }
                else
                {
                    // 正常の場合
                    return RedirectToAction("Index");
                }
            }
            return View(viewModel);
        }
    }
}
```

HTML

```html:Views/Home/Index.cshtml
@model ErrorWebApplication.Models.ErrorViewModel
@{
    ViewData["Title"] = "Error Page";
}

<h2>@ViewData["Title"].</h2>
<form asp-action="Index">
    <div class="form-horizontal">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="InputValue" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="InputValue" class="form-control" />
                <span asp-validation-for="InputValue" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <button type="submit" class="btn btn-primary">実行</button>
            </div>
        </div>
    </div>
</form>
```

## 実行結果
未入力で実行した場合(クライアント側でエラーチェック)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/76153/a0c218db-5691-a432-d6f9-4a09820995a7.png)

100より小さい値で入力した場合(サーバー側でエラーチェック)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/76153/bb4c213c-2f83-fe93-2ea0-7d4c8cc83a2e.png)


楽ちん！

# エラーをポップアップ表示する

上記コードを改造してポップアップ表示させてみます。

## コード例
HTML

```html:Views/Home/Index.cshtml
@model ErrorWebApplication.Models.ErrorViewModel
@{
    ViewData["Title"] = "Error Page";
}

<h2>@ViewData["Title"].</h2>
<form asp-action="Index">
    <div class="form-horizontal">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="InputValue" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="InputValue" class="form-control" />
                <span asp-validation-for="InputValue" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <button type="submit" class="btn btn-primary">実行</button>
            </div>
        </div>
    </div>
</form>
<!-- ここまでは同じ -->

<!-- エラーメッセージ -->
<div class="modal fade" id="errorModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title">エラー</h4>
            </div>
            <div class="modal-body text-danger">
                メッセージ
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">閉じる</button>
            </div>
        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    <script>
        $(function () {
            // クライアント側のエラー結果を受け取り、ポップアップ表示する処理
            $('form').each(function () {
                var validator = $(this).data('validator');
                validator.settings.showErrors = function (errorMap, errorList) {
                    if (this.errorList.length) {
                        this.defaultShowErrors();

                        var errorMessage = "";
                        $.each(errorList, function (index, value) {
                            errorMessage += value.message + '<br />';
                        });
                        showErrors(errorMessage);
                    }
                }
            });

            // サーバー側のエラー結果を受け取り、ポップアップ表示する処理
            $('.validation-summary-errors').each(function () {
                var errors = $(this).find("li");
                if (errors.length) {
                    var errorMessage = "";
                    $.each(errors, function (index, value) {
                        errorMessage += value.textContent + '<br />';
                    });
                    showErrors(errorMessage);
                }
            });

            // ポップアップ表示する処理
            function showErrors(errorMessage) {
                var modal = $('#errorModal');
                if (modal.data('bs.modal') == null || modal.data('bs.modal').isShown == false) {
                    modal.modal('show');
                    modal.find('.modal-body').html(errorMessage);
                }
            }
        });
    </script>
}
```


## 実行結果
クライアント側のエラー
![image.png](https://qiita-image-store.s3.amazonaws.com/0/76153/9ca670c6-cf69-1b5f-3e74-7347803a9079.png)

サーバー側のエラー
![image.png](https://qiita-image-store.s3.amazonaws.com/0/76153/59d26375-b517-2516-b5a6-ce9af2630b9f.png)

# [おまけ]もうちょっとエラー画面っぽくする

モーダルクラスのスタイルをいじるだけですが...

## コード例
HTML

```html:Views/Home/Index.cshtml
　：
（省略）
　：
<!-- エラーメッセージ -->
<div class="modal fade" id="errorModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content panel-danger">
            <div class="modal-header panel-heading" style="border-top-left-radius: inherit; border-top-right-radius: inherit;">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title"><i class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></i> エラー</h4>
            </div>
            <div class="modal-body text-danger">
                メッセージ
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">閉じる</button>
            </div>
        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->
　：
（省略）
　：
```

※divタグのmodal-contentクラスとpanel-headingクラスの２行を弄っているだけです。

## 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/76153/0ccba983-c9df-65c1-ab28-1f5b38fe48b9.png)

こんな感じ