ASP.NET MVC 5 でドロップダウンリストを使用する – ViewModel 編

ASP.NET MVC で、ドロップダウンリストを使用する方法について調べた結果をまとめてみました。

今回は ViewModel 編ということで、ドロップダウンリストの選択肢を表す ViewModel を作成し、Html.DropDownListFor メソッド を使用してドロップダウンリストを描画します。

環境

Windows 8.1 Home Edition
Visual Studio Community 2015 Update 2
ASP.NET MVC 5.2.3

目的

直近 10 年が選択肢となるドロップダウンリストを表示する。

手順

ビューモデルの作成

ここでは、以下の 2 つのビューモデル作成します。

  • 年のドロップダウンリストの選択肢(の 1 件)を表すビューモデル
  • ページ全体を表すビューモデル
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Tk2.MvcTips.Models
{
    // ドロップダウンリストの選択肢を表す ViewModel です。
    public class YearViewModel
    {
        // 選択肢の値をセットします。
        public int Value { get; set; }

        // 選択肢として表示するテキストを取得するためのプロパティ。
        // ここでは値に" 年"を足して、 "2016 年" と表示されるようにしています。
        public string DisplayText
        {
            get
            {
                return $"{this.Value} 年";
            }
        }
    }

    // ページに必要な情報を全て持つ ViewModel です。
    // View はこの ViewModel から値を取得するようにしています。
    public class DropDownListViewModel
    {
        // ドロップダウンリストで選択している値を取得または設定するためのプロパティです。
        [Required]
        [Display(Name = "年")]
        public int Year { get; set; }

        // ドロップダウンリストの選択肢をセットします。
        // ※ ViewBag で代用も可能。
        public IEnumerable<YearViewModel> YearOptions { get; set; }
    }
}

ポイントとしては、YearViewModel に、

  • ドロップダウンで選択した値と連動する Year プロパティ
  • 選択肢を設定するための YearOptions プロパティ

の両方を定義していることです。
YearOptions プロパティの目的は 「View に選択肢を渡すこと!」です。
なので、これは ViewBag でも代用可能です。

「MVC なのに何故 ビューモデルなの?モデルじゃないの?」と思われるかもしれませんが、以下の理由から ビューモデルとして実装しています。

  • ドロップダウンリストの選択肢はあくまで ビュー を意識したものなので、 モデル に持つのは違和感がある。
    (モデルというと Entity Framework などでそのまま DB にマッピングされる対象となるが、
    そこにドロップダウンリストの選択肢をもっているのはおかしいと思う為。)
  • ビューモデルを使用したほうが実用的だと思った為。
    MVC のスキャフォールディングで作成するような、
    単一のモデルをそのままビューに表示して CRUD 処理を行うようなページって、
    そんなに作らんのちゃうかなーと思っています。
    (モデルをそのままビューに出力するときは、選択肢は ViewBag で持つことになると思います。)

コントローラーの作成

コントローラーには、以下のメソッドを定義しています。

  • GET 用の Create メソッド
  • POST 用の Create メソッド
  • 年の選択肢を取得する GetYearOptions メソッド
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Tk2.MvcTips.Models;

namespace Tk2.MvcTips.Controllers
{
    public class DropDownListController : Controller
    {
        public ActionResult Create()
        {
            var vm = new DropDownListViewModel();

            // ここでビューモデルに選択肢をセットしています。
            vm.YearOptions = this.GetYearOptions();

            return View(vm);
        }

        [HttpPost]
        public ActionResult Create(DropDownListViewModel vm)
        {
            // もう一度ビューモデルに選択肢をセットしています。
            // Post されてこの箇所に来た時点で、
            // YearOptions プロパティは null になっているので、
            // 再度セットする必要があります。
            vm.YearOptions = this.GetYearOptions();

            return View(vm);
        }

        // 年の選択肢を取得するためのメソッドです。
        private IEnumerable<YearViewModel> GetYearOptions()
        {
            // 直近の 10 年を選択肢として取得する。
            return Enumerable
                .Range(DateTime.Now.Year - 9, 10)
                .Select(t => new YearViewModel() { Value = t });
        }
    }
}

注意点として、POST 用の Create メソッドでも、再度 DropDwonListViewModel の YearOptions プロパティに選択肢をセットしてやる必要があります。
(vm.YearOptions は、ここでは null になっています。)

ビューの作成

ビューでは、YearOptions プロパティを SelectList に変換し、Html.DropDownListFor メソッドを使用して、ドロップダウンリストを出力します。

@model Tk2.MvcTips.Models.DropDownListViewModel

@using (Html.BeginForm())
{
    <div class="form-horizontal">
        <h4>ドロップダウンリスト - ViewModel 編</h4>
        <hr />
        <div class="form-group">
            @Html.LabelFor(model => model.Year, htmlAttributes: new { @class = "control-label col-sm-2" })
            <div class="col-sm-10">

                @Html.DropDownListFor(
                    model => model.Year,
                    new SelectList(Model.YearOptions, "Value", "DisplayText"),
                    "年を選択",
                    new { @class = "form-control" })

                @Html.ValidationMessageFor(model => model.Year, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

結果

以下のように、ドロップダウンリストが出力されます。

tk2

映画とかゲームとかが好きです。 はやく引退して悠々自適な生活したい。

5件のコメントがあります

  1. ハル

    すみません、ご質問があります。
    この場合の(Valueではなく)「DisplayText」(20○○年)を
    コントローラーで値を取得するにはどうすればよろしいでしょうか?
    ネットや書籍で調べましたが、分からず悩んでおります。
    ご助言をいただきたく、コメントいたしました。
    お手数ですが、よろしくお願いいたします。

    1. アバター画像
      Tk2

      はじめまして。tk2 です。
      お役にたてず申し訳ないのですが DisplayText をコントローラー側で取得する方法は私も知りません・・・
      というのは、これまで DisplayText の値をコントローラ―側で取得したいという動機がなく、試したことがないからです。
      DisplayText はドロップダウンを選択する際にユーザーに見せる為のラベルなので、基本的に取得する必要がないと考えています。

      この投稿で例えると、コントローラー側でドロップダウンリストで選択された「2007 年」を取得できたとしても、DateTime 型に変換するために「年」のテキストを除去したりする必要が出てくるので、それならば初めから Value を取得したほうがよいのでは?という事になります。

      現在手元に環境もなく、試していないので憶測になりますが、サーバー側に来るパラメータの中にDisplayText は存在しないのではないでしょうか?
      ASP.NET MVC で取得する一般的な手段はないのではないかと思います。(ネットや書籍でも方法が見つからないのはそれが理由かも・・・)

  2. ハル

    ご連絡ありがとうございます。
    こちらで考えていたのは、ドロップダウンリストにデータベースのマスタを紐付けて
    内部的にはマスタのIDをValueに、マスタの名前を表示名(DisplayText)に使いたかったのです。
    (データベースの更新は、Value(マスタのID)を使い、データベース(マスタではなくトランザクション)の更新が完了した場合に、それが更新できたことをメールで送信するのに、マスタの名前(DisplayText)を表示名としてコントローラー側で使いたかったのです。そのトランザクションデータは正規化で上記マスタの表示名はカラムとしてないので。)

    ので、表示名がそのままコントローラー側で使える(ビューから渡せる)のであれば、そのまま使いたいなと思っておりました。

    無理なようであれば、コントローラー側で再度IDから表示名を引き直そうと思います。

    1. アバター画像
      Tk2

      事情をご連絡いただきありがとうございます。
      私も同様のシチュエーションですと、ハルさんと同じように再度 ID をキーにしてマスタを取得すると思います。
      特殊な事情で、「ビュー側に表示した時点の DisplayText をどうしても取得したい!」という要件があったらまた話は変わってくるのですが、そういったものがなければ上記が一番シンプルでよいのではないかと思います。

  3. ハル

    ご回答、ありがとうございました。
    がんばってみます。

コメントを残す