Question

I know there is a ListBoxFor extension method among the ASP.NET MVC Html helper extension methods, but I always thought that a checkbox list is more user-friendly than a list box.

There was a very convenient CheckBoxList control in good old WebForms, but obviously that is out of the picture now.

The question is, why is there no way in ASP.NET MVC to create a check box list? How can I write my own extension method that creates a check box list and behaves in a similar way ListBoxFor behaves?

Was it helpful?

Solution

Here is a strongly typed HtmlHelper for CheckBoxListFor that handles selected items as an array in your viewdata model. I chose not to wrapper the Html.CheckBox or Html.CheckBoxFor methods as I don't want the hidden "false" fields in my checkbox lists.

Please feel free to improve on this and repost :-)

//View

<%: Html.CheckBoxListFor(model => model.FreightTypeIds, FreightTypeMultiSelectList)  %>

//Controller

    public ActionResult SomeAction(int[] FreightTypeIds)
    {
       //...

       return View();
    }


//Extension
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList allOptions, object htmlAttributes = null)
{
    ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression<TModel, IEnumerable<TProperty>>(expression, htmlHelper.ViewData);

    // Derive property name for checkbox name
    string propertyName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(modelMetadata.PropertyName);

    // Get currently select values from the ViewData model
    IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);

    // Convert selected value list to a List<string> for easy manipulation
    IList<string> selectedValues = new List<string>();

    if (list != null)
    {
        selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
    }

    // Create div
    TagBuilder divTag = new TagBuilder("div");
    divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);

    // Add checkboxes
    foreach (SelectListItem item in allOptions)
    {
        divTag.InnerHtml += string.Format(
                                          "<div><input type=\"checkbox\" name=\"{0}\" id=\"{1}_{2}\" " +
                                          "value=\"{2}\" {3} /><label for=\"{1}_{2}\">{4}</label></div>",
                                          propertyName,
                                          TagBuilder.CreateSanitizedId(propertyName),
                                          item.Value,
                                          selectedValues.Contains(item.Value) ? "checked=\"checked\"" : string.Empty,
                                          item.Text);
    }

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

OTHER TIPS

I'm still experimenting but this seems to get along with the default binder and persists the user selections after post.. Hidden fields, really?? .. will this fly in html5 ? This feels crazy but I'd rather do this than hit my db for drop down lists and checkbox lists just because ModelState.IsValid is false..

        public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, List<SelectListItem> list, string ModelCollectionName)
    {
        var sb = new StringBuilder();

        if (list != null)
        {
            int i = 0;

            foreach (var l in list)
            {
                string collectionNameIndex = String.Format("{0}[{1}]", ModelCollectionName, i);

                var hiddenName = new TagBuilder("input");
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("type", "hidden"));
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("name", String.Format("{0}.{1}", collectionNameIndex, "Text")));
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("value", l.Text));

                var hiddenValue = new TagBuilder("input");
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("type", "hidden"));
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("name", String.Format("{0}.{1}", collectionNameIndex, "Value")));
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("value", l.Value));

                var checkBoxTag = htmlHelper.CheckBox(String.Format("{0}.{1}", collectionNameIndex, "Selected"), l.Selected);

                var labelTag = new TagBuilder("label");
                labelTag.Attributes.Add(new KeyValuePair<string, string>("for", String.Format("{0}.{1}", collectionNameIndex, "Name")));
                labelTag.SetInnerText(l.Text);

                sb.Append(hiddenName);
                sb.Append(hiddenValue);
                sb.Append(checkBoxTag);
                sb.Append(labelTag);
                sb.Append("<br/>");

                i++;
            }
        }

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

While Microsoft employees are probably the only ones that can answer why such helper method doesn't exist you could try:

Model:

public class MyViewModel
{
    public bool[] Values { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel 
        { 
            Values = new[] { true, false, true, false }
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

<% using (Html.BeginForm()) { %>
    <%: Html.EditorFor(x => x.Values) %>
    <input type="submit" value="OK" />
<% } %>

As you can see EditorFor will handle everything that's needed.

You might be interested in CheckBoxList Helper for MVC article by Jeremiah Clark (unfortunately it's dated Nov 2008 and concerns MVC 1).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top