228
votes

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" /> 
12
ericvg's answer in the possible duplicate question also explains what the model binder does when both the checkbox and hidden fields are submitted. - Theophilus
I hate this thing it was massing up with my jquery. - Jerry Liang
Weird implementation by mvc. Sending both values doesnt make sense at all. I checked Request.From["myCheckBox"] and its value was true, false. WTF. I had to write the control manually in the view. - Nick Masao
If this is really undesired then don't use Html.CheckBox(...) and just input the html for a checkbox - wnbates

12 Answers

208
votes

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.

35
votes

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})
13
votes

when the check box is checked and submitted perform this

if ($('[name="foo"]:checked').length > 0)
    $('[name="foo"]:hidden').val(true);

Refer

9
votes

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);
    }
}
8
votes

The manual approach is this:

bool IsDefault = (Request.Form["IsDefault"] != "false");
4
votes

Use Contains, it will work with the two possible post values: "false" or "true,false".

bool isChecked = Request.Form["foo"].Contains("true");
3
votes

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

2
votes

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>
2
votes

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);
}
1
votes

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">
0
votes

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')

0
votes

You can try to initialize the constructor of your Model like that :

public MemberFormModel() {
    foo = true;
}

and in your view :

@html.Checkbox(...)
@html.Hidden(...)