ASP.NET MVC アプリで、複数(数は不定)の CheckBox をレンダリングし、ユーザーがチェックを入れて POST したとき、どのチェックボックスがチェックされているかの状態を取得する方法を備忘録として書いておきます。
ユーザーがチェックを入れた CkeckBox からは true を、チェックしてない CheckBox からは false を取得できるようにするのが条件です。
ASP.NET Web Forms アプリですと CheckBoxList というサーバーコントロールがあって、それを使えば複数の CheckBox を実装するのは容易ですが、サーバーコントロールのない ASP.NET MVC アプリでは少々工夫が必要なようです。
html の input type="checkbox" ではチェックされた項目の name=value しか送ってこないのでかえって使いにくいです。Html.EditorFor を使うのがお勧めです。
List<bool> 型のオブジェクトをメンバーとして持つ Model を View に型付け、それから Html.EditorFor を使って CheckBox をレンダリングできます。
その CheckBox は html では input type="checkbox" だけでなく、それとペアで同じ name 属性を持つ隠しフィールド input type="hidden" value="false" も生成されます。(input type="hidden" を使うテクニックは MDN のドキュメント <input type="checkbox"> のメモ欄にも書かれています)
ユーザーがチェックを入れて POST すると、例えば name 属性が Item[0] だった場合、Items[0]=true&Items[0]=false というデータがフォームに含まれて送信されます。(チェックを入れない場合は隠しフィールドの Items[0]=false のみ)
ASP.NET は、モデルバインディングの際、それを見て List<bool> 型のオブジェクトの各要素を true / false に設定しているようです。
以下に、上の画像を表示したサンプルのコードとその説明を書いておきます。
Model と Controller
以下のサンプルではチェックされた項目は ture が、チェックされて無い項目は false がアクションメソッドの引数にバインドされるようにしています。(注:Model と Controller が一緒になっているのは単に分けるのが面倒だったからです)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Mvc4App.Models;
namespace Mvc4App.Controllers
{
// Model
public class CheckboxListModel
{
public CheckboxListModel()
{
Items = new List<bool>();
}
public IList<bool> Items { get; set; }
}
// Controller
public class ComplexController : Controller
{
[HttpGet]
public ActionResult Checkboxes()
{
// この例では CheckBox を 5 つ作成。初期値は false とし
// CheckBox はチェックされてない状態とする。
bool[] defaultItems =
new bool[] { false, false, false, false, false };
CheckboxListModel model =
new CheckboxListModel() { Items = defaultItems };
return View(model);
}
[HttpPost]
// POST されてきたデータは引数の m, items のいずれにもモデル
// バインドされる。items の方は Model のプロパティ名と一致さ
// せる必要がある。モデルバインディングの際は大文字・小文字
// が区別されないので引数名には小文字を使ってプロパティ
// Items に以下のように代入できる。
public ActionResult Checkboxes(CheckboxListModel m,
IList<bool> items)
{
CheckboxListModel model =
new CheckboxListModel() { Items = items };
return View(model);
}
}
}
View
EditorFor の引数で m => m.Items[i] としているところがキモです。そうするとレンダリングされる html 要素の name 属性が連番のインデックスを含むようになり、モデルバインディングがうまくいきます。
@model Mvc4App.Controllers.CheckboxListModel
@{
ViewBag.Title = "Checkboxes";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Checkboxes</h2>
@using (Html.BeginForm()) {
for (int i = 0; i < Model.Items.Count; i++)
{
@Html.EditorFor(m => m.Items[i])
<br />
}
<p>
<input type="submit" value="Send" />
</p>
}