134

In my ASP.NET MVC app, I am rendering a checkbox using the following code:

<%= Html.CheckBoxFor(i=>i.ReceiveRSVPNotifications) %>

Now, I see that this renders both the checkbox input tag and a hidden input tag. The problem that I am having is when I try retrieve the value from the checkbox using the FormCollection:

FormValues["ReceiveRSVPNotifications"]

I get the value "true,false". When looking at the rendered HTML, I can see the following:

 <input id="ReceiveRSVPNotifications" name="ReceiveRSVPNotifications" value="true" type="checkbox">
 <input name="ReceiveRSVPNotifications" value="false" type="hidden">

So the FormValues collection seems to join these two values since they have the same name.

Any Ideas?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Webcognoscere
  • 1,802
  • 5
  • 21
  • 29

5 Answers5

171

Have a look here:

http://forums.asp.net/t/1314753.aspx

This isn't a bug, and is in fact the same approach that both Ruby on Rails and MonoRail use.

When you submit a form with a checkbox, the value is only posted if the checkbox is checked. So, if you leave the checkbox unchecked then nothing will be sent to the server when in many situations you would want false to be sent instead. As the hidden input has the same name as the checkbox, then if the checkbox is unchecked you'll still get a 'false' sent to the server.

When the checkbox is checked, the ModelBinder will automatically take care of extracting the 'true' from the 'true,false'

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • 10
    That's a good explanation but in some cases you might want to get 'nothing' in the query string when the ckecbox is not checked - so the default behaviour. Is this possible using the Html helper or do I have to simply use the `` tag. – Maksymilian Majer Jun 21 '10 at 13:54
  • 13
    I dont like this....I have a search page that uses GET and now my url is full of true/false params... – Shawn Mclean Oct 03 '11 at 15:50
  • If you're using AJAX and passing specific params this does not work. There should be a way to control it, guess have to build our own checkbox implementation – James Reategui Feb 27 '12 at 17:53
  • 1
    Wow, that's unexpected. Does this mean that HTML checkbox is actually "broken"? – Isantipov Mar 30 '14 at 10:49
  • 1
    @Isantipov: No, it just means that these web frameworks don't rely on the *absence* of a posted value for a checkbox to indicate `false`. Look at RyanJMcGowan's answer below: *"Sending a hidden input makes it possible to know that the checkbox was present on the page when the request was submitted."* – Robert Harvey Mar 31 '14 at 00:39
  • Nice. I found this issue when using js to test the page before postback and checking checkbox field. It ended up always being true. I won't be looking for id then but for the name instead and try to get the hidden field instead. What a deceitful fail :-) – Mariusz Sep 05 '14 at 13:03
  • 1
    Well @Robert, this doesn't work for me. If my chbox _MyCheckbox_ is not checked then I get false, when the _MyCheckbox_ is checked then I get an array of [true,false] as its value. I am serializing the whole form and pass it through ajax to the controller action eg. AddToOrder(MyModel model) where I get model.MyCheckbox = false instead of true – nickornotto Aug 03 '15 at 13:28
  • The comments on the MSDN page helped me realize what was going on when clicking checkbox and the `value` for it didn't change. This is because the value in the `` doesn't actually matter until the form is submitted. – Freerey Jul 06 '22 at 15:20
18

I had the same problem as Shawn (above). This approach might be great for POST, but it really sucks for GET. Therefore I implemented a simple Html extension that just whips out the hidden field.

public static MvcHtmlString BasicCheckBoxFor<T>(this HtmlHelper<T> html, 
                                                Expression<Func<T, bool>> expression,
                                                object htmlAttributes = null)
{
    var result = html.CheckBoxFor(expression).ToString();
    const string pattern = @"<input name=""[^""]+"" type=""hidden"" value=""false"" />";
    var single = Regex.Replace(result, pattern, "");
    return MvcHtmlString.Create(single);
}

The problem I now have is that I don't want a change to the MVC framework to break my code. So I have to ensure I have test coverage explaining this new contract.

Chris Kemp
  • 2,069
  • 2
  • 18
  • 27
  • 5
    Don't you feel a bit dirty that you used regex to match/remove the hidden input rather than just building up _only_ a checkbox input? :) – Tim Hobbs May 03 '13 at 01:30
  • 2
    No, I did it so that I was amending the existing behaviour, rather than re-implementing my own. That way my change would keep pace with any changes MS rolled out. – Chris Kemp May 05 '13 at 23:29
  • 10
    Just a small observation. Your implementation will not render any custom attributes that are passed. The parameter "htmlAttributes" is not passed on to the original "CheckBoxFor" method. – Emil Lundin May 15 '13 at 07:45
17

I use this alternative method to render the checkboxes for GET forms:

/// <summary>
/// Renders checkbox as one input (normal Html.CheckBoxFor renders two inputs: checkbox and hidden)
/// </summary>
public static MvcHtmlString BasicCheckBoxFor<T>(this HtmlHelper<T> html, Expression<Func<T, bool>> expression, object htmlAttributes = null)
{
    var tag = new TagBuilder("input");

    tag.Attributes["type"] = "checkbox";
    tag.Attributes["id"] = html.IdFor(expression).ToString();
    tag.Attributes["name"] = html.NameFor(expression).ToString();
    tag.Attributes["value"] = "true";

    // set the "checked" attribute if true
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
    if (metadata.Model != null)
    {
        bool modelChecked;
        if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
        {
            if (modelChecked)
            {
                tag.Attributes["checked"] = "checked";
            }
        }
    }

    // merge custom attributes
    tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));

    var tagString = tag.ToString(TagRenderMode.SelfClosing);
    return MvcHtmlString.Create(tagString);
}

It's similar to Chris Kemp's method, which is working fine, except this one does not use the underlying CheckBoxFor and Regex.Replace. It's based on the source of the original Html.CheckBoxFor method.

Tom Pažourek
  • 9,582
  • 8
  • 66
  • 107
  • This is absolutely the best solution to this problem and should be part of the MVC framework... it works perfectly. I'm not sure why your answer has not received more attention... thanks! – user2315985 Sep 04 '15 at 09:39
  • @user2315985: I posted it a little bit too late ;) that's the reason. – Tom Pažourek Sep 04 '15 at 13:04
  • How are you handling collection properties? The default model binder will stop binding values when it encounters a gap in bound indexes, so it seems like using your extension, if, say, the first and last values were checked, you'd never know about the second value you should be receiving. Or, am I missing something? – Tieson T. Dec 11 '16 at 19:09
  • @TiesonT. Unless you want to write your own model binder, the only solution is to prevent the gaps. This method doesn't send false for unchecked checkboxes, so you might want to use the original Html.CheckBoxFor method that does that. – Tom Pažourek Dec 12 '16 at 06:53
10

Here is the source code for the additional input tag. Microsoft was kind enough to include comments that address this precisely.

if (inputType == InputType.CheckBox)
{
    // 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.
    StringBuilder inputItemBuilder = new StringBuilder();
    inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

    TagBuilder hiddenInput = new TagBuilder("input");
    hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
    hiddenInput.MergeAttribute("name", fullName);
    hiddenInput.MergeAttribute("value", "false");
    inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
    return MvcHtmlString.Create(inputItemBuilder.ToString());
}
KyleMit
  • 30,350
  • 66
  • 462
  • 664
RyanJMcGowan
  • 1,485
  • 1
  • 15
  • 33
10

I think that the simplest solution is to render the INPUT element directly as follows:

<input type="checkbox" 
       id="<%=Html.IdFor(i => i.ReceiveRSVPNotifications)%>"
       name="<%=Html.NameFor(i => i.ReceiveRSVPNotifications)%>"
       value="true"
       checked="<%=Model.ReceiveRSVPNotifications ? "checked" : String.Empty %>" />

In Razor syntax it is even easier, because the 'checked' attribute is directly rendered with a "checked" value when given a 'true' server-side value.

grammophone
  • 601
  • 6
  • 8