しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC で確認画面を表示させてみる

この記事にあるコードはバグがあり動作しません。こちらを参照してください。
ASP.NET MVC で確認画面を実装する(完全版) - しばやん雑記

デフォルトで生成される Create と Edit アクションには確認画面が用意されていませんね。

基本的にデフォルトのテンプレートで開発していたこともあり、確認画面の存在など特に考えなかったんですが、仕事では必ず確認画面が要求されるので表示するようにしたいと思います。*1

方針

普通に考えると確認画面が増えるわけなのでアクションを 1 つ増やして対応すればいい感じですね。アクションを増やすとすれば以下のような構成になると思います。

  • [HttpGet] Create
    • 入力フォームを表示
  • [HttpPost] CreateConfirm
    • 確認画面を表示
  • [HttpPost] Create
    • DB 登録

良さそうに見えますが、確認画面のアクション名が異なるので入力フォームと確認画面では POST 先を省略できないのでちゃんと指定する必要があります。

他にも値の引継ぎでセッションを使う必要があったり、ただ表示するだけのアクションが増えたりと手間なので、今回はアクションフィルタを使う方法を考えました。

Confirm 属性

今回、確認画面を表示するために ConfirmAttribute クラスを作成しました。それではさっそくコードを見てもらいます。

public class ConfirmAttribute : ActionFilterAttribute
{
    private const string BackButton = "Back";
    private const string ConfirmButton = "Confirm";

    private const string ConfirmViewSuffix = "Confirm";

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // アクションのパラメータとフォーム値を取得
        var parameter = filterContext.ActionParameters.First();
        var collection = filterContext.HttpContext.Request.Form;

        // 確認画面の表示、もしくは入力フォームへ戻るのか確認
        if (collection[ConfirmButton] != null || collection[BackButton] != null)
        {
            // テンポラリかアクションのパラメータから POST された値を取得
            var model = filterContext.Controller.TempData[parameter.Key] ?? parameter.Value;

            // 確認画面を表示する時のみテンポラリに格納
            if (collection[ConfirmButton] != null)
            {
                // 確認画面を表示する時にモデルエラーがある場合はデフォルトの処理を行う
                if (!filterContext.Controller.ViewData.ModelState.IsValid)
                {
                    return;
                }

                filterContext.Controller.TempData[parameter.Key] = model;
            }

            // [アクション名] + "Confirm" 名のビューを確認画面として表示
            filterContext.Result = new ViewResult
            {
                ViewName = filterContext.ActionDescriptor.ActionName + (collection[BackButton] != null ? "" : ConfirmViewSuffix),
                ViewData = new ViewDataDictionary(model),
                TempData = filterContext.Controller.TempData,
            };

            return;
        }

        // テンポラリの値をアクションのパラメータとして渡す
        filterContext.ActionParameters[parameter.Key] = filterContext.Controller.TempData[parameter.Key];
    }
}

割とコメントを付けたので読んでもらえるとわかると思いますが、アクションフィルタ内で確認画面を表示すべきタイミングかを調べて、定義されているビューを表示するようになっています。

表示すべきタイミングですが、今回は name 属性を付けた type="submit" の input タグで調べることにしました。

追記

クライアントサイド検証が無効になっている場合にモデルの検証が動作しなかったのを修正しました。

使い方

確認画面の表示という機能はこの属性の内部で完結しているため、Confirm 属性をポストされるアクションへ付けるだけで既存のコードを特に弄る必要なく使うことが出来ます。

//
// GET: /Product/Create

public ActionResult Create()
{
    return View();
}

//
// POST: /Product/Create

[Confirm]
[HttpPost]
public ActionResult Create(Product product)
{
    if (ModelState.IsValid)
    {
        db.Product.Add(product);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(product);
}

ビューには少し追加が必要になりますが、name 属性を付けるだけなので大した作業ではありません。確認画面へ進むボタンの name 属性の値は必ず "Confirm" としてください。

<p>
    <input type="submit" name="Confirm" value="Confirm" />
</p>

最後に肝心の確認画面ですが、適当に値を表示するだけのビューで問題ありません。submit が可能なボタンと必要であれば「戻る」ボタンがあれば十分です。

今回は Details テンプレートを使って作成したテンプレートに form タグを追加しただけの簡単なものを用意しました。

@model MvcApplication6.Models.Product

@{
    ViewBag.Title = "CreateConfirm";
}

<h2>CreateConfirm</h2>

@using (Html.BeginForm())
{
    <fieldset>
        <legend>Product</legend>

        <div class="display-label">Name</div>
        <div class="display-field">
            @Html.DisplayFor(model => model.Name)
        </div>

        <div class="display-label">Description</div>
        <div class="display-field">
            @Html.DisplayFor(model => model.Description)
        </div>

        <div class="display-label">Price</div>
        <div class="display-field">
            @Html.DisplayFor(model => model.Price)
        </div>

        <p>
            <input type="submit" name="Back" value="Back" />
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

重要なのはビューの名前で、必ず [アクション名] + "Confirm" という名前で作成してください。今回は Create アクション用の確認画面なので CreateConfirm.cshtml という名前になります。

動作確認

それではちゃんと動作するか確認したいと思います。まずは入力フォームに適当に値を入力します。

入力が終わったら Confirm ボタンをクリックします。すると CreateConfirm.cshtml で保存したしたビューが表示されます。

確認画面で Back ボタンをクリックすると入力フォームへ戻ります。入力した値は引き継がれています。

そして確認画面で Create ボタンをクリックすると、今まで通り DB へ保存する処理などが実行されます。Confirm 属性とビューを追加しただけですが、ちゃんと確認画面として動作していることが確認できました。

まとめ

確認画面は個人のサービスでは使われることは少ないかもしれないですが、必要な場面があれば活用してください。それにしても MVC のアクションフィルタはいろいろと使えますね。驚きました。

それでは僕は mvcConf @:Japan のスライド作成に戻ります!

*1:確認画面って、ひょっとしたら日本独特の文化だったりするんでしょうか?