ASP.NET MVC5 の Html ヘルパーの DropDownList および DropDownListFor に表示するデータを、アクションメソッドからビューにどのように渡すかということについて書きます。

上の画像は、先の記事「スキャフォールディング機能」で書いた通りにスキャフォールディング機能を使って自動生成させたコードで、その中の Edit 画面を表示したものです。
SupplierID と CategoryID が Html ヘルパーの DropDownList を使ってドロップダウン形式で表示されるようになっています。上の画像は CategoryID を展開したところで、CategoryName の一覧が表示されています。
スキャフォールディング機能で自動生成されたコードが基本になるでしょうから、それがどうなっているかを書きます。
まずアクションメソッド Edit で SelectList オブジェクトを生成し ViewBag に設定しています。以下のコードの通りです。
public ActionResult Edit(int? id)
{
NORTHWINDEntities db = new NORTHWINDEntities();
Products products = db.Products.Find(id);
ViewBag.CategoryID =
new SelectList(db.Categories, "CategoryID",
"CategoryName", products.CategoryID);
ViewBag.SupplierID =
new SelectList(db.Suppliers, "SupplierID",
"CompanyName", products.SupplierID);
return View(products);
}
コンストラクタに SelectList(IEnumerable, String, String, Object) を使って、第 4 引数に selectedValue を設定しているところに注目してください。これによりビューの DropDownList が html に変換された際、select 要素内の当該 option 要素に selected 属性が付与されます。
ビューの DropDownList のコードは以下のようになります。第 1 引数がアクションメソッドで設定した ViewBag のキー名、第 2 引数が null になっているところに注目してください。
@Html.DropDownList("SupplierID", null,
htmlAttributes: new { @class = "form-control" })
@Html.DropDownList("CategoryID", null,
htmlAttributes: new { @class = "form-control" })
DropDownList の第 2 引数が null となっていますが、第 2 引数の設定に関わらず ViewData / ViewBag から型が IEnumerable<SelectListItem> でキー名が第 1 引数と同じものを探してきます。
(例えば、上記のアクションメソッドで ViewBag.SupplierID の設定を削除すると、ビューの DropDownList のコードで「キー 'SupplierID' を持つ ViewData 項目の型は 'System.Int32' ですが、'IEnumerable<SelectListItem>' でなければなりません」というエラーになります)
DropDownList の第 1 引数を元に ViewBag で渡されたデータ(アクションメソッドで設定された SelectList オブジェクト)を取得するので、上の画像の通りドロップダウン形式で表示できるようになります。さらに、SelectList コンストラクタの第 4 引数に設定した selectedValue によって当該 option 要素に selected 属性が設定された結果が表示されます。
なお、DropDownList の第 2 引数を (SelectList)ViewBag.Supplier としたりすると、SelectList のコンストラクタで第 4 引数に設定した selectedValue が無視されるので注意してください。理由は不明です。
ViewData / ViewBag に DropDownList の第 1 引数と同じキー名がない場合は、DropDownList の第 2 引数の設定が有効になるようです。例えば、アクションメソッドで ViewBag.SupplierID を ViewBag.Supplier に変更した場合、DropDownList("SupplierID", (SelectList)ViewBag.Supplier, ...)
として selected の設定を含めて期待した結果が得られます。
ViewData / ViewBag を探す順序ですが、検証してみると、まず最初に ViewData を、それになければ ViewBag を探すという結果になりました。ViewData / ViewBag に同じキー名があると、ViewData のデータが使われます。その際、もし ViewData のデータが不正ですと(IEnumerable<SelectListItem> 型でないと)エラーになります。
以上は DropDownListFor を使っても同様です。第 1 引数は model => model.SupplierID のようにします。このケースでは View に渡す Model を Products クラスとしており、その中に SupplierID プロパティが含まれています。そうでないと model.SupplierID はエラーになるので注意してください。
しかし、DropDownListFor は Products クラスの SupplierID プロパティからデータを取得するのではなく、プロパティ名 SupplierID から ViewData / ViewBag を探してその SelectList オブジェクトをリストに設定してくれます。