しばやん雑記

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

ASP.NET MVC でチェックボックス周りを良い感じに扱う

ASP.NET MVC の HTML ヘルパーには CheckBox/CheckBoxFor メソッドが用意されていますが、これを使って実現できるのは bool でのオンオフが必要なチェックボックスだけです。

しかし、世の中には「当てはまる項目にチェックを入れてください」というようなチェックボックスに複数値を持たせるフォームが存在します。

ITpro会員100万人に聞く!ICT大調査 - 個人契約のクラウドサービス、仕事に使ってますか?:ITpro

f:id:shiba-yan:20130228002348p:plain

このようなフォームを作るための機能は ASP.NET MVC には標準で入っていません。という訳で ASP.NET MVC でラジオボタン周りを良い感じに扱う - しばやん雑記 と同様に CheckBoxListFor というヘルパーを作成して解決してみました。

public static class CheckBoxHelpers
{
    public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        var name = ExpressionHelper.GetExpressionText(expression);

        var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        var values = ((IEnumerable)metadata.Model ?? new object[0]).Cast<object>().Select(p => p.ToString()).ToList();

        var result = new StringBuilder();

        var selectList = (IEnumerable<SelectListItem>)htmlHelper.ViewData.Eval(name);

        foreach (var selectListItem in selectList)
        {
            var value = selectListItem.Value ?? selectListItem.Text;

            var id = fullName + "-" + value;

            var tagBuilder = new TagBuilder("input");

            tagBuilder.MergeAttribute("id", id);
            tagBuilder.MergeAttribute("type", "checkbox");
            tagBuilder.MergeAttribute("name", fullName, true);
            tagBuilder.MergeAttribute("value", value);

            if (values.Contains(value))
            {
                tagBuilder.MergeAttribute("checked", "checked");
            }

            result.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

            var labelBuilder = new TagBuilder("label");

            labelBuilder.MergeAttribute("for", id);
            labelBuilder.InnerHtml = selectListItem.Text;

            result.Append(labelBuilder.ToString(TagRenderMode.Normal));

            result.Append("<br />");
        }

        return MvcHtmlString.Create(result.ToString());
    }
}

コード的には RadioButtonListFor の時と殆ど変っていないですが、今回のヘルパーはモデルのプロパティの型としてコレクションを受け取るようにしています。つまり、以下のようなフォームモデルを用意することで良い感じに値を扱えるようになっています。

public class FormModel
{
    public int[] Attributes { get; set; }
}

そして実際に動かしてみるとこんな感じです。ちゃんとチェックした項目は POST で戻ってきてもチェックが保持されます。

f:id:shiba-yan:20130228003405p:plain

そろそろ作成したヘルパー類をちゃんと例外処理も実装して GitHub で公開しようかなぁとか思ってます。