今回のお題
MVCでViewにmultipleなselectを配置するメソッド[HtmlHelper.ListBoxFor<>]。
コイツを使用した際に、デフォルトで選択状態にする(selected属性を付ける)ことができない場合があったので、
備忘録的に書いておきます。
条件
- モデルに初期値を持っている
- ListBoxForで使用する選択肢(SelectListItemクラスのリスト)をViewBagに持っている
- モデルで初期値を持っているプロパティ名と、選択肢を持たせたViewBagのプロパティ名が一致している
発生する問題
ListBoxForによって作成されたselectタグに、モデルで渡したはずの初期値が反映されない。
先に解決策を書いておく
[今回のお題].[条件]を読んでいただいた方にはもう大方の予想はついていると思います。
選択肢を格納しておくViewBagのプロパティ名と、バインド先のModelのプロパティ名が一致していと上手くいかないので、プロパティ名を変更しましょう。
環境
Visual Studio Community 2019 (16.8.3)
Windows10 Pro (2004) 64bit
.NET Framework 4.8
再現するコード
至ってシンプルに書いていきます。
Model
プロパティ[Fruits]だけ持つモデル
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace ListPropertyStudyNET.Models
{
public class BasketVM
{
[Display(Name = "フルーツ")]
public List<string> Fruits { get; set; }
}
}
View
モデルの[Fruits]プロパティにバインドし、項目をViewBag.Fruitsから持ってくるListBoxForのみ配置。
@model ListPropertyStudyNET.Models.BasketVM
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div>
@Html.ListBoxFor(m => m.Fruits, (IEnumerable<SelectListItem>)ViewBag.Fruits, new { @class = "form-control"})
</div>
Controller
ViewBag.Fruitsに選択肢、BasketVM.Fruitsに初期値を設定してIndex.cshtmlを返すだけ。
using ListPropertyStudyNET.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace ListPropertyStudyNET.Controllers
{
public class ShoppingController : Controller
{
// GET: Shopping
public ActionResult Index()
{
//リスト用
List<SelectListItem> items = new List<SelectListItem>()
{
new SelectListItem()
{
Text = "リンゴ",
Value = "apple"
},
new SelectListItem()
{
Text = "オレンジ",
Value = "orange"
},
new SelectListItem()
{
Text = "バナナ",
Value = "banana"
}
};
//リスト用のプロパティをViewBagに放り込む
ViewBag.Fruits = items;
//オレンジとバナナを初期選択にしたい
BasketVM vm = new BasketVM()
{
Fruits = new List<string>()
{
"orange", "banana"
}
};
return View(vm);
}
}
}
いざ実行
このように、Modelに設定した初期値(オレンジ、バナナの選択)が反映されていません。
コードの修正
Controller
リストの選択肢を設定するViewBagのプロパティ名を変更しています。
using ListPropertyStudyNET.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace ListPropertyStudyNET.Controllers
{
public class ShoppingController : Controller
{
// GET: Shopping
public ActionResult Index()
{
//リスト用
List<SelectListItem> items = new List<SelectListItem>()
{
new SelectListItem()
{
Text = "リンゴ",
Value = "apple"
},
new SelectListItem()
{
Text = "オレンジ",
Value = "orange"
},
new SelectListItem()
{
Text = "バナナ",
Value = "banana"
}
};
//リスト用のプロパティをViewBagに放り込む
//ViewBag.Fruits = items; //※この行を削除して
ViewBag.FruitsItems = items; //※この行を追加。ViewBagに設定したプロパティ名が[Fruits → FruitsItems]となりました。
//オレンジとバナナを初期選択にしたい
BasketVM vm = new BasketVM()
{
Fruits = new List<string>()
{
"orange", "banana"
}
};
return View(vm);
}
}
}
View
ViewBagのプロパティ名変更に伴い、コード内で参照するプロパティを変更しています。
@model ListPropertyStudyNET.Models.BasketVM
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div>
@*@Html.ListBoxFor(m => m.Fruits, (IEnumerable<SelectListItem>)ViewBag.Fruits, new { @class = "form-control"})*@
@Html.ListBoxFor(m => m.Fruits, (IEnumerable<SelectListItem>)ViewBag.FruitsItems, new { @class = "form-control"})
</div>
再度実行してみる
オレンジとバナナに[selected="selected"]が付与されていることがわかります。
まとめ
というわけで、ListBoxForを使用する場合、選択肢を格納しておくViewBagのプロパティ名と、バインド先のModelのプロパティ名が一致している場合、初期選択が反映されないようです。
原因……ご存じの方がいらっしゃったらコメントで教えてください。
以上です。