asp.net mvc: pourquoi Html.CheckBox génère-t-il une entrée cachée supplémentaire

215

Je viens de remarquer que Html.CheckBox("foo")génère 2 entrées au lieu d'une, tout le monde sait pourquoi en est-il ainsi?

<input id="foo" name="foo" type="checkbox" value="true" />
<input name="foo" type="hidden" value="false" /> 
Omu
la source
3
la duplication possible de la méthode HTML.Checkbox
Matt
1
La réponse d'ericvg dans la question en double possible explique également ce que fait le classeur de modèles lorsque la case à cocher et les champs masqués sont soumis.
Theophilus
1
Je déteste ce truc qui s'accumulait avec ma requête.
Jerry Liang
2
Implémentation bizarre par mvc. L'envoi des deux valeurs n'a aucun sens. J'ai vérifié Request.From ["myCheckBox"] et sa valeur était true, false. WTF. J'ai dû écrire le contrôle manuellement dans la vue.
Nick Masao
Si cela n'est vraiment pas souhaité, n'utilisez pas Html.CheckBox (...) et entrez simplement le
code

Réponses:

198

Si la case n'est pas cochée, le champ du formulaire n'est pas soumis. C'est pourquoi il y a toujours une fausse valeur dans un champ caché. Si vous ne cochez pas la case, le formulaire aura toujours la valeur du champ masqué. Voilà comment ASP.NET MVC gère les valeurs des cases à cocher.

Si vous souhaitez confirmer cela, cochez une case sur le formulaire non pas avec Html.Hidden, mais avec <input type="checkbox" name="MyTestCheckboxValue"></input>. Ne cochez pas la case, soumettez le formulaire et examinez les valeurs de demande publiées côté serveur. Vous verrez qu'il n'y a pas de valeur de case à cocher. Si vous aviez un champ caché, il contiendrait une MyTestCheckboxValueentrée avec une falsevaleur.

LukLed
la source
52
Si vous n'avez pas coché la case, la réponse est évidemment fausse, pas besoin de renvoyer l'article. Conception idiote à mon avis.
The Muffin Man
54
@TheMuffinMan: Ce n'est pas une option stupide. Supposons que votre modèle de vue possède une propriété appelée IsActive, qui est initiée truedans le constructeur. L'utilisateur désélectionne la case à cocher, mais comme la valeur n'est pas envoyée au serveur, le classeur de modèle ne la récupère pas et la valeur de la propriété n'est pas modifiée. Le classeur de modèles ne doit pas supposer que si la valeur n'est pas envoyée, elle est définie sur false, car il pourrait être votre décision de ne pas envoyer cette valeur.
LukLed
21
Il commence par une case à cocher inoffensive, puis avant que vous ne le sachiez, nous avons l'état d'affichage, puis il évolue en ASP.NET MVCForms.
The Muffin Man
4
@LukLed En réponse à votre commentaire tardivement ... Je pense que la logique est défectueuse car si votre modèle de vue a un type de propriété int et qu'il n'y a pas d'entrée sur le formulaire, la valeur par défaut est 0 car aucune valeur n'est venue pour le changer. Il en va de même pour toute autre propriété, la valeur par défaut d'une chaîne est nulle. Si vous n'envoyez pas de valeur, elle est nulle. Dans votre exemple de définition d'une propriété sur true dans le constructeur, c'est honnêtement une mauvaise décision de conception et cela se révèle maintenant à cause du fonctionnement des cases à cocher dans le pays http.
The Muffin Man
8
Cette logique d'occultation se casse pour les cases à cocher désactivées - elles envoient de la falsevaleur même si elles sont cochées, ce qui est déroutant. Les cases à cocher désactivées ne doivent pas envoyer de valeur du tout, si ASP.NET veut être compatible avec le comportement HTTP par défaut.
JustAMartin
31

Vous pouvez écrire un assistant pour empêcher l'ajout de l'entrée masquée:

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

utilise le:

@Html.CheckBoxSimple("foo", new {value = bar.Id})
Alexander Trofimov
la source
4
Cela m'a fait trébucher pendant une minute, donc au cas où ... Si votre assistant est dans un espace de noms, n'oubliez pas d'ajouter @using Your.Name.Spaceen haut de votre fichier de vue de rasoir .cshtml.
ooXei1sh du
3
@ ooXei1sh Soit cela, soit placez votre assistant dans l'espace System.Web.Mvc.Htmlde noms pour qu'il soit accessible sur toutes les vues
Luke
1
Si vous souhaitez l'utiliser pour une publication de formulaire ou pour une publication via ajax avec les valeurs de formulaire, vous devrez gérer la définition de la valeur true ou false via l'événement onchange dans la case à cocher. @ Html.CheckBoxSimple (x => Model.Foo, new {value = Model.Foo, onchange = "toggleCheck (this)"}). Puis dans la fonction javascript ToggleCompleted (el) {var vérifié = $ (el) .is (': vérifié'); $ ('# Foo'). Val (vérifié); }
John81
13

lorsque la case est cochée et soumise, effectuez cette opération

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

Référer

Desmond
la source
8

L'approche manuelle est la suivante:

bool IsDefault = (Request.Form["IsDefault"] != "false");
Valamas
la source
1
Et est-ce garanti que vous obtiendrez la valeur du type de case à cocher et non la valeur du type masqué?
Brian Sweeney
1
cela n'est pas pertinent. Lorsque la case n'est pas cochée, elle n'est pas publiée. Ainsi, la boîte cachée aura «faux». Si la case est cochée, la valeur de «vrai, vrai» est renvoyée (je pense), et cela n'est pas égal à «faux».
Valamas
16
Non, lorsque la case est cochée, true et false sont publiés car la case à cocher et les champs masqués sont des contrôles valides à renvoyer. Le liant Mvc vérifie ensuite si la vraie valeur existe pour un nom donné et si c'est le cas, il préfère la vraie valeur. Vous pouvez le vérifier en consultant les données publiées. Il aura des valeurs vraies et fausses par rapport à un seul nom.
ZafarYousafi
Cela m'a eu une fois, je vérifiais si la valeur == true
Terry Kernan
8

Voici la version fortement typée de la solution d'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
la source
4

Utilisez Contient, cela fonctionnera avec les deux valeurs de publication possibles: "false" ou "true, false".

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

L'entrée masquée causait des problèmes avec les cases à cocher stylisées. J'ai donc créé une extension d'assistance HTML pour placer l'entrée cachée en dehors de la div contenant la 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());
        }

Résultat

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

Ce n'est pas un bug! Il ajoute la possibilité d'avoir toujours une valeur, après avoir posté le formulaire sur le serveur. Si vous souhaitez traiter les champs de saisie de case à cocher avec jQuery, utilisez la méthode prop (passez la propriété 'checked' comme paramètre). Exemple:$('#id').prop('checked')

BlueKore
la source
0

Vous pouvez essayer d'initialiser le constructeur de votre modèle comme ça:

public MemberFormModel() {
    foo = true;
}

et selon vous:

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

J'ai trouvé que cela causait vraiment des problèmes lorsque j'avais un WebGrid. Les liens de tri sur le WebGrid tourneraient par la chaîne de requête doublée ou x = true & x = false en x = true, false et provoqueraient une erreur d'analyse dans la case à cocher pour.

J'ai fini par utiliser jQuery pour supprimer les champs cachés côté client:

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