Why does the CheckBoxFor render an additional input tag, and how can I get the value using the FormCollection?
asp.netasp.net Mvcasp.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.