Why does the CheckBoxFor render an additional input tag, and how can I get the value using the FormCollection?

asp.netasp.net Mvc

asp.net Problem Overview


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?

asp.net Solutions


Solution 1 - asp.net

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'

Solution 2 - asp.net

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.

Solution 3 - asp.net

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.

Solution 4 - asp.net

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());
}

Solution 5 - asp.net

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionWebcognoscereView Question on Stackoverflow
Solution 1 - asp.netRobert HarveyView Answer on Stackoverflow
Solution 2 - asp.netChris KempView Answer on Stackoverflow
Solution 3 - asp.netTom PažourekView Answer on Stackoverflow
Solution 4 - asp.netRyanJMcGowanView Answer on Stackoverflow
Solution 5 - asp.netgrammophoneView Answer on Stackoverflow