asp.net mvc: ¿por qué Html.CheckBox genera una entrada oculta adicional?

215

Acabo de notar que Html.CheckBox("foo")genera 2 entradas en lugar de una, ¿alguien sabe por qué es así?

<input id="foo" name="foo" type="checkbox" value="true" />
<input name="foo" type="hidden" value="false" /> 
Omu
fuente
1
La respuesta de ericvg en la posible pregunta duplicada también explica qué hace el cuaderno de modelo cuando se envían tanto la casilla de verificación como los campos ocultos.
Theophilus
1
Odio esta cosa que se estaba acumulando con mi jquery.
Jerry Liang
2
Implementación extraña por mvc. Enviar ambos valores no tiene ningún sentido. Verifiqué Request.From ["myCheckBox"] y su valor era verdadero, falso. WTF Tuve que escribir el control manualmente en la vista.
Nick Masao
Si esto no es realmente deseado, entonces no use Html.CheckBox (...) y solo ingrese el html para una casilla de verificación
wnbates

Respuestas:

197

Si la casilla de verificación no está seleccionada, el campo del formulario no se envía. Es por eso que siempre hay un valor falso en el campo oculto. Si deja la casilla de verificación sin marcar, el formulario seguirá teniendo valor del campo oculto. Así es como ASP.NET MVC maneja los valores de las casillas de verificación.

Si desea confirmar eso, coloque una casilla de verificación en el formulario no con Html.Hidden, sino con <input type="checkbox" name="MyTestCheckboxValue"></input>. Deje la casilla sin marcar, envíe el formulario y observe los valores de solicitud publicados en el lado del servidor. Verá que no hay un valor de casilla de verificación. Si tuviera un campo oculto, contendría una MyTestCheckboxValueentrada con falsevalor.

LukLed
fuente
52
Si no marcó la casilla, entonces obviamente la respuesta es falsa, no es necesario que envíe el artículo. Diseño tonto en mi opinión.
The Muffin Man
54
@TheMuffinMan: Esta no es una opción tonta. Digamos que su modelo de vista tiene una propiedad llamada IsActive, que se inicia trueen constructor. El usuario deselecciona la casilla de verificación, pero dado que el valor no se envía al servidor, el cuaderno de modelo no lo recoge y el valor de la propiedad no cambia. El cuaderno de modelo no debe suponer que si el valor no se envía, se estableció en falso, ya que podría ser su decisión no enviar este valor.
LukLed
21
Comienza con una casilla de verificación inofensiva y luego, antes de que te des cuenta, tenemos un estado de vista, luego evoluciona a ASP.NET MVCForms.
The Muffin Man
44
@LukLed Respondiendo a su comentario tarde ... Creo que la lógica es defectuosa porque si su modelo de vista tiene un tipo de propiedad int y no hay entrada en el formulario, entonces el valor predeterminado es 0 porque no se ingresó ningún valor para cambiarlo. Lo mismo ocurre con cualquier otra propiedad, el valor predeterminado para una cadena es nulo. Si no envía un valor, es nulo. En su ejemplo de establecer una propiedad como verdadera en el constructor, esa es honestamente una mala decisión de diseño y ahora sale a la luz debido a cómo funcionan las casillas de verificación en http land.
The Muffin Man
8
Esta lógica de sombreado se rompe para las casillas de verificación deshabilitadas: envían falsevalor incluso si están marcadas, y eso es confuso. Las casillas de verificación deshabilitadas no deberían enviar ningún valor, si ASP.NET quiere ser compatible con el comportamiento HTTP predeterminado.
JustAMartin
31

Puede escribir un ayudante para evitar agregar la entrada oculta:

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

úsalo:

@Html.CheckBoxSimple("foo", new {value = bar.Id})
Alexander Trofimov
fuente
44
Esto me hizo tropezar por un minuto, así que por si acaso ... Si su ayudante está en un espacio de nombres, no olvide agregar @using Your.Name.Spaceen la parte superior de su archivo de vista de afeitadora .cshtml.
ooXei1sh
3
@ ooXei1sh O eso, o poner su ayudante en el espacio System.Web.Mvc.Htmlde nombres para que sea accesible en todas las vistas
Lucas
1
Si desea utilizar esto para una devolución de datos del formulario o para publicar a través de ajax con los valores del formulario, deberá manejar la configuración del valor en verdadero o falso a través del evento onchange en la casilla de verificación. @ Html.CheckBoxSimple (x => Model.Foo, nuevo {value = Model.Foo, onchange = "toggleCheck (this)"}). Luego, en la función de JavaScript ToggleCompleted (el) {var check = $ (el) .is (': checked'); $ ('# Foo'). Val (marcado); }
John81
12

cuando la casilla de verificación está marcada y enviada, realice esto

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

Referir

Desmond
fuente
8

El enfoque manual es este:

bool IsDefault = (Request.Form["IsDefault"] != "false");
Valamas
fuente
1
¿Y esto garantiza obtener el valor de tipo de casilla de verificación y no el valor de tipo oculto?
Brian Sweeney el
1
Eso es irrelevante. Cuando la casilla de verificación no está marcada, no se publica. Entonces el hiddenbox tendrá 'falso'. Si la casilla está marcada, se devuelve el valor de 'verdadero, verdadero' (creo), y esto no es igual a 'falso'.
Valamas
16
No, cuando se marca la casilla de verificación, se publican tanto verdadero como falso porque tanto la casilla de verificación como los campos ocultos son controles válidos para ser devueltos. Mvc binder luego comprueba si existe un valor verdadero para una namve dada y, de ser así, prefiere el valor verdadero. Puede verificar esto viendo los datos publicados. Tendrá valores verdaderos y falsos contra un solo nombre.
ZafarYousafi
Esto me consiguió una vez, estaba comprobando si el valor == verdadero
Terry Kernan
8

Esta es la versión fuertemente tipada de la solución de Alexander Trofimov:

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);
    }
}
Ciro Corvino
fuente
4

Use Contains, funcionará con los dos valores de publicación posibles: "falso" o "verdadero, falso".

bool isChecked = Request.Form["foo"].Contains("true");
Dave D
fuente
1

La entrada oculta estaba causando problemas con las casillas de estilo. Así que creé una extensión de ayuda HTML para colocar la entrada oculta fuera del div que contiene 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());
        }

Resultado

<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">
VDWWD
fuente
0

¡Esto no es un error! Agrega la posibilidad de tener siempre un valor, después de publicar el formulario en el servidor. Si desea lidiar con los campos de entrada de la casilla de verificación con jQuery, use el método prop (pase la propiedad 'marcada' como parámetro). Ejemplo:$('#id').prop('checked')

BlueKore
fuente
0

Puede intentar inicializar el constructor de su modelo así:

public MemberFormModel() {
    foo = true;
}

y en tu opinión:

@html.Checkbox(...)
@html.Hidden(...)
Saïd Mezhoud
fuente
0

Descubrí que esto realmente causaba problemas cuando tenía un WebGrid. Los enlaces de clasificación en WebGrid pasarían por la cadena de consulta duplicada o x = true & x = false en x = true, false y causarían un error de análisis en la casilla de verificación.

Terminé usando jQuery para eliminar los campos ocultos en el lado del cliente:

    <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>
Matthew Lock
fuente