asp.net mvc: why is Html.CheckBox generating an additional hidden input
asp.net Mvcasp.net Mvc Problem Overview
I just noticed that Html.CheckBox("foo")
generates 2 inputs instead of one, anybody knows why is this so ?
<input id="foo" name="foo" type="checkbox" value="true" />
<input name="foo" type="hidden" value="false" />
asp.net Mvc Solutions
Solution 1 - asp.net Mvc
If checkbox is not selected, form field is not submitted. That is why there is always false value in hidden field. If you leave checkbox unchecked, form will still have value from hidden field. That is how ASP.NET MVC handles checkbox values.
If you want to confirm that, place a checkbox on form not with Html.Hidden, but with <input type="checkbox" name="MyTestCheckboxValue"></input>
. Leave checkbox unchecked, submit form and look at posted request values on server side. You'll see that there is no checkbox value. If you had hidden field, it would contain MyTestCheckboxValue
entry with false
value.
Solution 2 - asp.net Mvc
You can write a helper to prevent adding the hidden input:
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class HelperUI
{
public static MvcHtmlString CheckBoxSimple(this HtmlHelper htmlHelper, string name, object htmlAttributes)
{
string checkBoxWithHidden = htmlHelper.CheckBox(name, htmlAttributes).ToHtmlString().Trim();
string pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1));
return new MvcHtmlString(pureCheckBox);
}
}
use it:
@Html.CheckBoxSimple("foo", new {value = bar.Id})
Solution 3 - asp.net Mvc
when the check box is checked and submitted perform this
if ($('[name="foo"]:checked').length > 0)
$('[name="foo"]:hidden').val(true);
Solution 4 - asp.net Mvc
This is the strongly typed version of Alexander Trofimov's solution:
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class HelperUI
{
public static MvcHtmlString CheckBoxSimpleFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression, object htmlAttributes)
{
string checkBoxWithHidden = htmlHelper.CheckBoxFor(expression, htmlAttributes).ToHtmlString().Trim();
string pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1));
return new MvcHtmlString(pureCheckBox);
}
}
Solution 5 - asp.net Mvc
The manual approach is this:
bool IsDefault = (Request.Form["IsDefault"] != "false");
Solution 6 - asp.net Mvc
Beginning with ASP.NET (Core) 5, add this to your Startup:
services.Configure<MvcViewOptions>(options =>
{
// Disable hidden checkboxes
options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None;
});
In your view for example:
<input class="form-check-input" asp-for="@Model.YourBool" />
An additional hidden field for this property is no longer created in your form:
<input class="form-check-input" type="checkbox" data-val="true" data-val-required="The YourBool field is required." id="YourBool" name="YourBool" value="true" />
Source: https://github.com/dotnet/aspnetcore/pull/13014#issuecomment-674449674
Solution 7 - asp.net Mvc
Use Contains, it will work with the two possible post values: "false" or "true,false".
bool isChecked = Request.Form["foo"].Contains("true");
Solution 8 - asp.net Mvc
I found this really caused issues when I had a WebGrid. The sorting links on the WebGrid would turn by the doubled up querystring or x=true&x=false into x=true,false and cause a parse error in checkbox for.
I ended up using jQuery to delete the hidden fields on the client side:
<script type="text/javascript">
$(function () {
// delete extra hidden fields created by checkboxes as the grid links mess this up by doubling the querystring parameters
$("input[type='hidden'][name='x']").remove();
});
</script>
Solution 9 - asp.net Mvc
As of 2020/11 and .NET 5 being in preview, there is a pull request that should make this behavior controllable. Thank you guys!
Anyway if someone founds it useful, .NET Core 3.0 port of Alexander Trofimov's answer:
public static IHtmlContent CheckBoxSimple(this IHtmlHelper htmlHelper, string name)
{
TextWriter writer = new StringWriter();
IHtmlContent html = htmlHelper.CheckBox(name);
html.WriteTo(writer, HtmlEncoder.Default);
string checkBoxWithHidden = writer.ToString();
string pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1));
return new HtmlString(pureCheckBox);
}
Solution 10 - asp.net Mvc
The hidden input was causing problems with styled checkboxes. So I created a Html Helper Extension to place the hidden input outside the div containing the CheckBox.
using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
namespace YourNameSpace
{
public static class HtmlHelperExtensions
{
public static MvcHtmlString CustomCheckBoxFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, string labelText)
{
//get the data from the model binding
var fieldName = ExpressionHelper.GetExpressionText(expression);
var fullBindingName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var modelValue = metaData.Model;
//create the checkbox
TagBuilder checkbox = new TagBuilder("input");
checkbox.MergeAttribute("type", "checkbox");
checkbox.MergeAttribute("value", "true"); //the visible checkbox must always have true
checkbox.MergeAttribute("name", fullBindingName);
checkbox.MergeAttribute("id", fieldId);
//is the checkbox checked
bool isChecked = false;
if (modelValue != null)
{
bool.TryParse(modelValue.ToString(), out isChecked);
}
if (isChecked)
{
checkbox.MergeAttribute("checked", "checked");
}
//add the validation
checkbox.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(fieldId, metaData));
//create the outer div
var outerDiv = new TagBuilder("div");
outerDiv.AddCssClass("checkbox-container");
//create the label in the outer div
var label = new TagBuilder("label");
label.MergeAttribute("for", fieldId);
label.AddCssClass("checkbox");
//render the control
StringBuilder sb = new StringBuilder();
sb.AppendLine(outerDiv.ToString(TagRenderMode.StartTag));
sb.AppendLine(checkbox.ToString(TagRenderMode.SelfClosing));
sb.AppendLine(label.ToString(TagRenderMode.StartTag));
sb.AppendLine(labelText); //the label
sb.AppendLine("<svg width=\"10\" height=\"10\" class=\"icon-check\"><use xlink:href=\"/icons.svg#check\"></use></svg>"); //optional icon
sb.AppendLine(label.ToString(TagRenderMode.EndTag));
sb.AppendLine(outerDiv.ToString(TagRenderMode.EndTag));
//create the extra hidden input needed by MVC outside the div
TagBuilder hiddenCheckbox = new TagBuilder("input");
hiddenCheckbox.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
hiddenCheckbox.MergeAttribute("name", fullBindingName);
hiddenCheckbox.MergeAttribute("value", "false");
sb.Append(hiddenCheckbox.ToString(TagRenderMode.SelfClosing));
//return the custom checkbox
return MvcHtmlString.Create(sb.ToString());
}
Result
<div class="checkbox-container">
<input checked="checked" id="Model_myCheckBox" name="Model.myCheckBox" type="checkbox" value="true">
<label class="checkbox" for="Model_myCheckBox">
The checkbox label
<svg width="10" height="10" class="icon-check"><use xlink:href="/icons.svg#check"></use></svg>
</label>
</div>
<input name="Model.myCheckBox" type="hidden" value="false">
Solution 11 - asp.net Mvc
This is not a bug! It adds the possibility of having always a value, after posting the form to the server.
If you want to deal with checkbox input fields with jQuery, use the prop method (pass the 'checked' property as the parameter).
Example: $('#id').prop('checked')
Solution 12 - asp.net Mvc
You can try to initialize the constructor of your Model like that :
public MemberFormModel() {
foo = true;
}
and in your view :
@html.Checkbox(...)
@html.Hidden(...)