ASP.NET Core MVCで確認画面を実装する

## 環境
asp.net core 2.1
visual studio 2019
SQL Server Express LocalDB
bootstrap4

## はじめに
映画の新規登録時の確認画面を実装していきます。
タグヘルパーとフィルターを使用していますが、内容の説明を入れていないので分からない場合は調べてください。

[ASP.NET Core タグヘルパー](https://docs.microsoft.com/ja-jp/aspnet/core/mvc/views/tag-helpers/authoring?view=aspnetcore-2.1)
[ASP.NET Core フィルター](https://docs.microsoft.com/ja-jp/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.1)

今回のサンプルプロジェクトはGitHubに置いてあります。
https://github.com/koro1129/ConfirmScreen


## 1. タグヘルパーを自作して、独自の「確認」「戻る」「追加」ボタンを生成できるようにする。
### 1-1 プロジェクト直下に「TagHelpers」フォルダを作成する。
### 1-2 以下のクラスを追加する。
```c#:TagHelpers/ConfirmTagHelper.cs

    // 確認用ボタン
    public class ConfirmTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "input";
            output.Attributes.Add("type","submit");
            output.Attributes.Add("name", "__ConfirmButton"); //←ここが重要
        }
    }
```

```c#:TagHelpers/SubmitTagHelper.cs

    // 登録用ボタン
    public class SubmitTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "input";
            output.Attributes.Add("type","submit");
            output.Attributes.Add("name", "__SubmitButton"); //←ここが重要
        }
    }
```

```c#:TagHelpers/BackTagHelper.cs

    // 戻る用ボタン
    public class BackTagHelper : TagHelper
    {
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "input";
            output.Attributes.Add("type", "submit");
            output.Attributes.Add("name", "__BackButton"); //←ここが重要
        }
    }
```

### 1-3 Views/Shared/_ViewImports.cshtmlに設定を追加。

※Areaなどを使用してViewフォルダが複数ある場合は、使用するAreaの「_ViewImports.cshtml」に記載する。

```html:Views/Shared/_ViewImports.cshtml

    @using ConfirmScreen
    @using ConfirmScreen.Models
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *, [アセンブリ名]   //← これを追加（[アセンブリ名]には、基本的にプロジェクト名をいれればいいと思う）
```
上記を追加することで「1-2」で作成したタグヘルパーをView内で使用できるようになります。

### 1-4 登録画面のform内に「確認」ボタンを配置する。
```html:Views/Movies/Create.cshtml
<form asp-action="Create">
    <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">
        <confirm value="確認する" class="btn btn-default bg-primary"></confirm> //←これを追加
    </div>
</form>
```
- タグを入力する際は「confirm」とだけ打ってもインテリセンスに表示されないので、＜confim＞と打つといい。
- valueやclassやidはそのまま入力すればよい。

実際に表示される際は以下のタグで生成される。
![kakunin.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337557/bc12f542-15bb-ce2d-3179-32337bb68583.png)![tagu.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337557/9618fd43-5fc8-9e08-c208-742c3bf33adc.png)



## 2. 「1」で配置したボタン押下時の処理をFilterで定義する。
### 2-1 プロジェクト直下に「Filter」フォルダを作成する。
### 2-2 以下のクラスを追加する。
```C#:Filter/ConfirmAttribute.cs

namespace MyNameSpace.Filter{

    // 確認画面描画用のアクションフィルター
    public class ConfirmAttribute : ActionFilterAttribute
    {
        private const string SubmitButtonKey = "__SubmitButton";        //　←自作したタグヘルパーのname属性と同じであること。
        private const string ConfirmButtonKey = "__ConfirmButton";      //　←自作したタグヘルパーのname属性と同じであること。
        private const string BackButtonKey = "__BackButton";            //　←自作したタグヘルパーのname属性と同じであること。

        private const string ViewSuffix = "Confirm";

        // アクションメソッド実行前処理
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var form = context.HttpContext.Request.Form;

            var factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;

            // ※Post先アクションメソッド(EditとかCreate)の第一引数と、確認画面で定義する「@model」は同じ型であること。※
            var parameter = context.ActionArguments.FirstOrDefault();

            // 登録ボタンが押された場合
            if (form.Any(f => f.Key == SubmitButtonKey))
            {
                // そのままアクションメソッドのpost処理を実行する。
                return;
            }

            // 確認ボタンが押された場合
            var viewName = (string)context.RouteData.Values["Action"];
            if (form.Any(f => f.Key == ConfirmButtonKey))
            {
                // モデルの検証でエラーが発生しているか調べる
                if (!context.ModelState.IsValid)
                {
                    // Viewに戻りエラーを表示する
                    return;
                }

                // 確認画面を表示するためにビュー名を変更
                viewName += ViewSuffix;
            }

            var controller = context.Controller as Controller;

            // ビューを表示する（戻るボタンを押した場合は入力内容そのままで戻る）
            context.Result = new ViewResult
            {
                ViewName = viewName,
                // Viewにモデルの内容を渡すために必要。ViewのModelに値が入る。
                ViewData = new ViewDataDictionary(controller.ViewData) { Model = parameter.Value }
            };
        }

        // アクションメソッド実行後処理
        public override void OnActionExecuted(ActionExecutedContext context)
        {
        }
    }
}
```

### 2-3 post先のアクションメソッドに、作成したフィルターを適用する設定を追加する。

```c#:Controllers/MoviesController.cs

        // 登録処理
        [HttpPost]
        [ValidateAntiForgeryToken]
        [MyNameSpace.Filter.ConfirmAttribute] //　←　これを追加(namespaceはプロジェクトごとに変更してください。)
        public async Task<IActionResult> Create(MoviewCreateViewModel createVM)
        {
            if (ModelState.IsValid)
            {
                _context.Add(createVM.Movie);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(createVM);
        }
```
[MyNameSpace.Filter.ConfirmAttribute] を追加することで、アクションメソッドが実行される前に定義したフィルターの処理を行うことができる。


## 3. 確認画面用のViewを作成する。
### 3-1 確認用のcshtmlファイルを作成して、「戻る」「登録」ボタンを配置する。

※Viewのファイル名は「アクションメソッド名+Confirm」にする。（今回は「CreateConfirm」となる）

```html:Views/Movies/CreateConfirm.cshtml
        <form asp-action="Create">
            <div class="form-group">
                <label asp-for="Movie.Title" class="control-label"></label>：
                <input type="hidden" asp-for="Movie.Title" class="form-control" />
                <span>@Model.Movie.Title</span>
            </div>
            <!-- 割愛 -->
            <div class="form-group">
                <back value="戻る" class="btn btn-default bg-primary"></back>        // ←これを追加
                <submit value="登録" class="btn btn-default bg-primary"></submit>    // ←これを追加
            </div>
        </form>
```
表示の内容は適当に整えてください。
ただ、inputタグを忘れると戻った際と登録時に内容が保持されないので注意してください。


## 4. 実際に動かす
### 登録画面
![a.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337557/879913c8-b127-41f1-382a-f804154fe6d5.png)
### 確認画面
![b.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337557/544eee34-43bd-5f4a-9500-78819922158e.png)
### 登録画面エラーあり
![c.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337557/ca5bb963-2935-5913-faff-8b160a0139b7.png)

## 終わりに
・確認項目が単純な場合は、手順がわかっていればすぐに確認画面ができるので楽です。
・懸念すべきことは、「Filter/ConfirmAttribute.cs」内で「ITempDataDictionaryFactory」というのが使用されているので、ロードバランサーのようにアプリケーションサーバーが複数台ある場合はうまく動かなかったりするかもしれません。


## 参考サイト
http://igatea.hatenablog.com/entry/2018/05/31/002648
https://blog.shibayan.jp/entry/20111010/1318258722 
https://stackoverflow.com/questions/41200597/access-tempdata-in-executeresult-asp-net-mvc-core
https://forums.asp.net/t/2141597.aspx?Get+Controller+Name+From+Filter+Context
https://tutorialmore.com/questions-1553730.htm