How to handle checkboxes in ASP.NET MVC forms?

Viewed 237960

Caution: This question is over nine years old!

Your best option is to search for newer questions, or to search the answers below looking for your specific version of MVC, as many answers here are obsolete now.

If you do find an answer that works for your version, please make sure the answer contains the version of MVC you are using.
(The original question starts below)


This seems a bit bizarre to me, but as far as I can tell, this is how you do it.

I have a collection of objects, and I want users to select one or more of them. This says to me "form with checkboxes." My objects don't have any concept of "selected" (they're rudimentary POCO's formed by deserializing a wcf call). So, I do the following:

public class SampleObject{
  public Guid Id {get;set;}
  public string Name {get;set;}
}

In the view:

<%
    using (Html.BeginForm())
    {
%>
  <%foreach (var o in ViewData.Model) {%>
    <%=Html.CheckBox(o.Id)%>&nbsp;<%= o.Name %>
  <%}%>
  <input type="submit" value="Submit" />
<%}%>

And, in the controller, this is the only way I can see to figure out what objects the user checked:

public ActionResult ThisLooksWeird(FormCollection result)
{
  var winnars = from x in result.AllKeys
          where result[x] != "false"
          select x;
  // yadda
}

Its freaky in the first place, and secondly, for those items the user checked, the FormCollection lists its value as "true false" rather than just true.

Obviously, I'm missing something. I think this is built with the idea in mind that the objects in the collection that are acted upon within the html form are updated using UpdateModel() or through a ModelBinder.

But my objects aren't set up for this; does that mean that this is the only way? Is there another way to do it?

22 Answers

Html.CheckBox is doing something weird - if you view source on the resulting page, you'll see there's an <input type="hidden" /> being generated alongside each checkbox, which explains the "true false" values you're seeing for each form element.

Try this, which definitely works on ASP.NET MVC Beta because I've just tried it.

Put this in the view instead of using Html.CheckBox():

<% using (Html.BeginForm("ShowData", "Home")) {  %>
  <% foreach (var o in ViewData.Model) { %>
    <input type="checkbox" name="selectedObjects" value="<%=o.Id%>">
    <%= o.Name %>
  <%}%>
  <input type="submit" value="Submit" />
<%}%>

Your checkboxes are all called selectedObjects, and the value of each checkbox is the GUID of the corresponding object.

Then post to the following controller action (or something similar that does something useful instead of Response.Write())

public ActionResult ShowData(Guid[] selectedObjects) {
    foreach (Guid guid in selectedObjects) {
        Response.Write(guid.ToString());
    }
    Response.End();
    return (new EmptyResult());
}

This example will just write the GUIDs of the boxes you checked; ASP.NET MVC maps the GUID values of the selected checkboxes into the Guid[] selectedObjects parameter for you, and even parses the strings from the Request.Form collection into instantied GUID objects, which I think is rather nice.

HtmlHelper adds an hidden input to notify the controller about Unchecked status. So to have the correct checked status:

bool bChecked = form[key].Contains("true");

In case you're wondering WHY they put a hidden field in with the same name as the checkbox the reason is as follows :

Comment from the sourcecode MVCBetaSource\MVC\src\MvcFutures\Mvc\ButtonsAndLinkExtensions.cs

Render an additional <input type="hidden".../> for checkboxes. This addresses scenarios where unchecked checkboxes are not sent in the request. Sending a hidden input makes it possible to know that the checkbox was present on the page when the request was submitted.

I guess behind the scenes they need to know this for binding to parameters on the controller action methods. You could then have a tri-state boolean I suppose (bound to a nullable bool parameter). I've not tried it but I'm hoping thats what they did.

You should also use <label for="checkbox1">Checkbox 1</label> because then people can click on the label text as well as the checkbox itself. Its also easier to style and at least in IE it will be highlighted when you tab through the page's controls.

<%= Html.CheckBox("cbNewColors", true) %><label for="cbNewColors">New colors</label>

This is not just a 'oh I could do it' thing. Its a significant user experience enhancement. Even if not all users know they can click on the label many will.

Here's what I've been doing.

View:


<input type="checkbox" name="applyChanges" />

Controller:


var checkBox = Request.Form["applyChanges"];

if (checkBox == "on")
{
...
}

I found the Html.* helper methods not so useful in some cases, and that I was better off doing it in plain old HTML. This being one of them, the other one that comes to mind is radio buttons.

Edit: this is on Preview 5, obviously YMMV between versions.

They appear to be opting to read the first value only, so this is "true" when the checkbox is checked, and "false" when only the hidden value is included. This is easily fetched with code like this:

model.Property = collection["ElementId"].ToLower().StartsWith("true");

I'd also like to point out that you can name each checkbox a different name, and have that name part of the actionresults parameters.

Example,

View:

 <%= Html.CheckBox("Rs232CheckBox", false, new { @id = "rs232" })%>RS-232

 <%= Html.CheckBox("Rs422CheckBox", false, new { @id = "rs422" })%>RS-422

Controller:

public ActionResults MyAction(bool Rs232CheckBox, bool Rs422CheckBox) {
    ...
}

The values from the view are passed to the action since the names are the same.

I know this solution isn't ideal for your project, but I thought I'd throw the idea out there.

This issue is happening in the release 1.0 as well. Html.Checkbox() causes another hidden field to be added with the same name/id as of your original checkbox. And as I was trying loading up a checkbox array using document.GetElemtentsByName(), you can guess how things were getting messed up. It's a bizarre.

My solution is:

<input type="checkbox"  id="IsNew-checkbox"  checked="checked" />     
<input type="hidden"  id="IsNew" name="IsNew" value="true"  />      
<script language="javascript" type="text/javascript" >     
  $('#IsNew-checkbox').click(function () {    
      if ($('#IsNew-checkbox').is(':checked')) {    
          $('#IsNew').val('true');    
      } else {    
          $('#IsNew').val('false');    
       }    
  });   
</script>  

More you can find here: http://www.blog.mieten.pl/2010/12/asp-net-mvc-custom-checkbox-as-solution-of-string-was-not-recognized-as-a-valid-boolean/

Related