¿Por qué CheckBoxFor representa una etiqueta de entrada adicional y cómo puedo obtener el valor usando FormCollection?

130

En mi aplicación ASP.NET MVC, estoy representando una casilla de verificación con el siguiente código:

<%= Html.CheckBoxFor(i=>i.ReceiveRSVPNotifications) %>

Ahora, veo que esto hace que tanto la etiqueta de entrada casilla de verificación y una etiqueta de entrada oculta. El problema que tengo es cuando intento recuperar el valor de la casilla de verificación usando FormCollection:

FormValues["ReceiveRSVPNotifications"]

Me sale el valor "verdadero, falso". Al mirar el HTML renderizado, puedo ver lo siguiente:

 <input id="ReceiveRSVPNotifications" name="ReceiveRSVPNotifications" value="true" type="checkbox">
 <input name="ReceiveRSVPNotifications" value="false" type="hidden">

Entonces, la colección FormValues ​​parece unir estos dos valores ya que tienen el mismo nombre.

¿Algunas ideas?

Webcognoscere
fuente

Respuestas:

168

Echa un vistazo aquí:

http://forums.asp.net/t/1314753.aspx

Esto no es un error, y de hecho es el mismo enfoque que usan Ruby on Rails y MonoRail.

Cuando envía un formulario con una casilla de verificación, el valor solo se publica si la casilla de verificación está marcada. Por lo tanto, si deja la casilla de verificación sin marcar, no se enviará nada al servidor cuando, en muchas situaciones, desee que se envíe falso en su lugar. Como la entrada oculta tiene el mismo nombre que la casilla de verificación, si la casilla de verificación no está marcada, aún recibirá un mensaje 'falso' enviado al servidor.

Cuando la casilla de verificación está marcada, ModelBinder se encargará automáticamente de extraer el 'verdadero' del 'verdadero, falso'

Robert Harvey
fuente
10
Esa es una buena explicación, pero en algunos casos es posible que desee obtener 'nada' en la cadena de consulta cuando el ckecbox no está marcado, por lo que el comportamiento predeterminado. ¿Es esto posible usando el ayudante Html o simplemente tengo que usar la <input>etiqueta?
Maksymilian Majer
13
No me gusta esto ... Tengo una página de búsqueda que usa GET y ahora mi url está llena de parámetros verdaderos / falsos ...
Shawn Mclean
1
Wow, eso es inesperado. ¿Esto significa que la casilla de verificación HTML está realmente "rota"?
Isantipov
1
@Isantipov: No, solo significa que estos marcos web no se basan en la ausencia de un valor publicado para que una casilla de verificación indique false. Mire la respuesta de RyanJMcGowan a continuación: "Enviar una entrada oculta hace posible saber que la casilla de verificación estaba presente en la página cuando se envió la solicitud".
Robert Harvey
1
Bueno @Robert, esto no funciona para mí. Si mi chbox MyCheckbox no está marcado, obtengo false, cuando MyCheckbox está marcado, obtengo una matriz de [true, false] como su valor. Estoy serializando todo el formulario y lo paso a través de ajax a la acción del controlador, por ejemplo. AddToOrder (modelo MyModel) donde obtengo model.MyCheckbox = false en lugar de true
nickornotto
18

Tuve el mismo problema que Shawn (arriba). Este enfoque puede ser excelente para POST, pero realmente apesta para GET. Por lo tanto, implementé una extensión Html simple que simplemente elimina el campo oculto.

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

El problema que tengo ahora es que no quiero un cambio en el marco MVC para romper mi código. Así que debo asegurarme de tener una cobertura de prueba que explique este nuevo contrato.

Chris Kemp
fuente
55
¿No se siente un poco sucia que ha utilizado expresiones regulares para que coincida con / eliminar la entrada oculta en lugar de sólo la construcción de solamente una entrada de casilla? :)
Tim Hobbs
2
No, lo hice para enmendar el comportamiento existente, en lugar de volver a implementar el mío. De esa manera, mi cambio mantendría el ritmo de cualquier cambio que MS implementara.
Chris Kemp
10
Solo una pequeña observación. Su implementación no representará ningún atributo personalizado que se pase. El parámetro "htmlAttributes" no se pasa al método original "CheckBoxFor".
Emil Lundin
16

Utilizo este método alternativo para representar las casillas de verificación de los formularios GET:

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

Es similar al método de Chris Kemp , que funciona bien, excepto que este no utiliza el subyacente CheckBoxFory Regex.Replace. Se basa en la fuente del Html.CheckBoxFormétodo original .

Tom Pažourek
fuente
Esta es absolutamente la mejor solución a este problema y debería ser parte del marco MVC ... funciona perfectamente. No estoy seguro de por qué su respuesta no ha recibido más atención ... ¡gracias!
user2315985
@ user2315985: lo publiqué demasiado tarde;) esa es la razón.
Tom Pažourek
¿Cómo maneja las propiedades de la colección? El enlazador de modelo predeterminado detendrá los valores de enlace cuando encuentre una brecha en los índices enlazados, por lo que parece usar su extensión, si, por ejemplo, se verificaron los primeros y últimos valores, nunca sabría sobre el segundo valor que debería recibir. . ¿O me estoy perdiendo algo?
Tieson T.
@TiesonT. A menos que desee escribir su propio modelo de carpeta, la única solución es evitar las brechas. Este método no envía false para casillas de verificación sin marcar, por lo que es posible que desee utilizar el método Html.CheckBoxFor original que lo hace.
Tom Pažourek
10

Aquí está el código fuente de la etiqueta de entrada adicional. Microsoft tuvo la amabilidad de incluir comentarios que abordan esto con precisión.

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

Creo que la solución más simple es representar el elemento INPUT directamente de la siguiente manera:

<input type="checkbox" 
       id="<%=Html.IdFor(i => i.ReceiveRSVPNotifications)%>"
       name="<%=Html.NameFor(i => i.ReceiveRSVPNotifications)%>"
       value="true"
       checked="<%=Model.ReceiveRSVPNotifications ? "checked" : String.Empty %>" />

En la sintaxis de Razor es aún más fácil, porque el atributo 'verificado' se representa directamente con un valor "verificado" cuando se le da un valor 'verdadero' del lado del servidor.

gramófono
fuente