Pourquoi CheckBoxFor affiche-t-il une balise d'entrée supplémentaire et comment puis-je obtenir la valeur à l'aide de FormCollection?

130

Dans mon application ASP.NET MVC, j'effectue le rendu d'une case à cocher à l'aide du code suivant:

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

Maintenant, je vois que cela rend à la fois la balise d'entrée de case à cocher et une balise d'entrée masquée. Le problème que je rencontre est lorsque j'essaye de récupérer la valeur de la case à cocher à l'aide de FormCollection:

FormValues["ReceiveRSVPNotifications"]

J'obtiens la valeur "vrai, faux". En regardant le HTML rendu, je peux voir ce qui suit:

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

Ainsi, la collection FormValues ​​semble joindre ces deux valeurs puisqu'elles portent le même nom.

Des idées?

Webcognoscere
la source

Réponses:

168

Regardez ici:

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

Ce n'est pas un bogue, et c'est en fait la même approche que Ruby on Rails et MonoRail utilisent.

Lorsque vous soumettez un formulaire avec une case à cocher, la valeur n'est publiée que si la case est cochée. Donc, si vous laissez la case décochée, rien ne sera envoyé au serveur alors que dans de nombreuses situations, vous voudriez que false soit envoyé à la place. Comme l'entrée masquée a le même nom que la case à cocher, si la case est décochée, vous obtiendrez toujours un «faux» envoyé au serveur.

Lorsque la case est cochée, ModelBinder se chargera automatiquement d'extraire le 'vrai' du 'vrai, faux'

Robert Harvey
la source
10
C'est une bonne explication, mais dans certains cas, vous voudrez peut-être obtenir «rien» dans la chaîne de requête lorsque la ckecbox n'est pas cochée - donc le comportement par défaut. Est-ce possible en utilisant l'assistant Html ou dois-je simplement utiliser la <input>balise.
Maksymilian Majer
13
Je n'aime pas ça .... J'ai une page de recherche qui utilise GET et maintenant mon url est pleine de vrais / faux paramètres ...
Shawn Mclean
1
Wow, c'est inattendu. Cela signifie-t-il que la case à cocher HTML est réellement "cassée"?
Isantipov
1
@Isantipov: Non, cela signifie simplement que ces frameworks Web ne reposent pas sur l' absence d'une valeur publiée pour qu'une case à cocher l'indique false. Regardez la réponse de RyanJMcGowan ci-dessous: "L'envoi d'une entrée masquée permet de savoir que la case à cocher était présente sur la page lorsque la demande a été soumise."
Robert Harvey
1
Eh bien @Robert, cela ne fonctionne pas pour moi. Si ma chbox MyCheckbox n'est pas cochée alors j'obtiens false, quand MyCheckbox est cochée alors j'obtiens un tableau de [true, false] comme valeur. Je sérialise le formulaire entier et le passe via ajax à l'action du contrôleur, par exemple. AddToOrder (modèle MyModel) où j'obtiens model.MyCheckbox = false au lieu de true
nickornotto
18

J'ai eu le même problème que Shawn (ci-dessus). Cette approche peut être excellente pour POST, mais elle est vraiment nulle pour GET. Par conséquent, j'ai implémenté une simple extension Html qui ne fait que supprimer le champ caché.

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

Le problème que j'ai maintenant est que je ne veux pas qu'une modification du framework MVC brise mon code. Je dois donc m'assurer d'avoir une couverture de test expliquant ce nouveau contrat.

Chris Kemp
la source
5
Ne vous sentez un sale peu que vous avez utilisé pour correspondre regex / supprimer l'entrée cachée plutôt que construire seulement une entrée de case à cocher? :)
Tim Hobbs
2
Non, je l'ai fait pour modifier le comportement existant, plutôt que de réimplémenter le mien. De cette façon, mon changement suivrait le rythme de tous les changements déployés par MS.
Chris Kemp
10
Juste une petite observation. Votre implémentation ne rendra aucun attribut personnalisé transmis. Le paramètre "htmlAttributes" n'est pas transmis à la méthode "CheckBoxFor" d'origine.
Emil Lundin
16

J'utilise cette méthode alternative pour afficher les cases à cocher des formulaires 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);
}

C'est similaire à la méthode de Chris Kemp , qui fonctionne bien, sauf que celle-ci n'utilise pas le sous CheckBoxFor- jacent et Regex.Replace. Il est basé sur la source de la Html.CheckBoxForméthode originale .

Tom Pažourek
la source
C'est absolument la meilleure solution à ce problème et devrait faire partie du framework MVC ... cela fonctionne parfaitement. Je ne sais pas pourquoi votre réponse n'a pas reçu plus d'attention ... merci!
user2315985
@ user2315985: Je l'ai posté un peu trop tard;) c'est la raison.
Tom Pažourek le
Comment gérez-vous les propriétés de la collection? Le classeur de modèle par défaut arrêtera de lier les valeurs lorsqu'il rencontre un espace dans les index liés, il semble donc que vous utilisiez votre extension, si, par exemple, les première et dernière valeurs étaient vérifiées, vous ne saurez jamais quelle est la deuxième valeur que vous devriez recevoir . Ou est-ce que je manque quelque chose?
Tieson T.Décembre
@TiesonT. À moins que vous ne souhaitiez écrire votre propre classeur de modèles, la seule solution est d'éviter les lacunes. Cette méthode n'envoie pas false pour les cases à cocher non cochées, vous pouvez donc utiliser la méthode Html.CheckBoxFor d'origine qui fait cela.
Tom Pažourek
10

Voici le code source de la balise d'entrée supplémentaire. Microsoft a eu la gentillesse d'inclure des commentaires qui traitent précisément de cela.

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
la source
9

Je pense que la solution la plus simple est de rendre l'élément INPUT directement comme suit:

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

Dans la syntaxe Razor, c'est encore plus facile, car l'attribut 'vérifié' est directement rendu avec une valeur "vérifié" lorsqu'il est donné une valeur côté serveur "vraie".

grammophone
la source