Comment gérer les cases à cocher dans les formulaires ASP.NET MVC?

246

Attention: cette question a plus de neuf ans!

Votre meilleure option est de rechercher de nouvelles questions ou de rechercher les réponses ci-dessous à la recherche de votre version spécifique de MVC, car de nombreuses réponses sont obsolètes maintenant.

Si vous trouvez une réponse qui fonctionne pour votre version, assurez-vous que la réponse contient la version de MVC que vous utilisez.
(La question d'origine commence ci-dessous)


Cela me semble un peu bizarre, mais pour autant que je sache, c'est comme ça que vous le faites.

J'ai une collection d'objets et je souhaite que les utilisateurs en sélectionnent un ou plusieurs. Cela me dit "formulaire avec des cases à cocher". Mes objets n'ont aucun concept de "sélectionné" (ce sont des POCO rudimentaires formés en désérialisant un appel wcf). Donc, je fais ce qui suit:

public class SampleObject{
  public Guid Id {get;set;}
  public string Name {get;set;}
}

Dans la vue:

<%
    using (Html.BeginForm())
    {
%>
  <%foreach (var o in ViewData.Model) {%>
    <%=Html.CheckBox(o.Id)%>&nbsp;<%= o.Name %>
  <%}%>
  <input type="submit" value="Submit" />
<%}%>

Et, dans le contrôleur, c'est la seule façon dont je peux voir pour déterminer quels objets l'utilisateur a vérifiés:

public ActionResult ThisLooksWeird(FormCollection result)
{
  var winnars = from x in result.AllKeys
          where result[x] != "false"
          select x;
  // yadda
}

C'est bizarre en premier lieu, et deuxièmement, pour les éléments que l'utilisateur a vérifiés, le FormCollection répertorie sa valeur comme "vrai faux" plutôt que juste vrai.

De toute évidence, il me manque quelque chose. Je pense que cela est construit avec l'idée à l'esprit que les objets de la collection qui sont traités dans le formulaire html sont mis à jour à l'aide UpdateModel()ou via un ModelBinder.

Mais mes objets ne sont pas configurés pour cela; cela signifie-t-il que c'est le seul moyen? Y a-t-il une autre façon de procéder?

Uwe Keim
la source
2
D'autres peuvent trouver cette solution utile: stackoverflow.com/questions/3291501/…
Aaron

Réponses:

262

Html.CheckBox fait quelque chose de bizarre - si vous affichez la source sur la page résultante, vous verrez qu'il y a un <input type="hidden" />être généré à côté de chaque case à cocher, ce qui explique les "vraies fausses" valeurs que vous voyez pour chaque élément de formulaire.

Essayez ceci, qui fonctionne définitivement sur ASP.NET MVC Beta car je viens de l'essayer.

Mettez cela dans la vue au lieu d'utiliser Html.CheckBox ():

<% using (Html.BeginForm("ShowData", "Home")) {  %>
  <% foreach (var o in ViewData.Model) { %>
    <input type="checkbox" name="selectedObjects" value="<%=o.Id%>">
    <%= o.Name %>
  <%}%>
  <input type="submit" value="Submit" />
<%}%>

Vos cases à cocher sont toutes appelées selectedObjectsetvalue case à cocher de chaque est le GUID de l'objet correspondant.

Ensuite, publiez sur l'action de contrôleur suivante (ou quelque chose de similaire qui fait quelque chose d'utile au lieu de Response.Write ())

public ActionResult ShowData(Guid[] selectedObjects) {
    foreach (Guid guid in selectedObjects) {
        Response.Write(guid.ToString());
    }
    Response.End();
    return (new EmptyResult());
}

Cet exemple va simplement écrire les GUID des cases que vous avez cochées; ASP.NET MVC mappe les valeurs GUID des cases à cocher sélectionnées dans le Guid[] selectedObjectsparamètre pour vous, et analyse même les chaînes de la collection Request.Form en objets GUID instanciés, ce qui, je pense, est plutôt agréable.

Dylan Beattie
la source
Ouaip! C'est aussi ce que j'ai dû faire pour mes applications!
Adhip Gupta
70
wtf. champs de saisie masqués avec le MÊME nom que le contrôle. son ViewState 2.0!
Simon_Weaver
3
Ceci est également présent dans la version 1.0. Regardez la réponse @ andrea-balducci soumise qui vous donne un moyen intelligent de gérer cela. Si la case n'est pas cochée, le texte résultant récupéré devrait être «faux faux» - c'est une bonne solution pour tenir compte des navigateurs morts du cerveau ...
Bryan Rehbein
77
Surprise? Wtf? L'entrée cachée porte le même nom que la case à cocher - si la case à cocher du même nom n'est pas cochée, sa valeur n'est pas publiée tandis que la valeur du caché est publiée. La première fois que le navigateur rencontre un élément nommé, il utilise cette valeur et ignore tous les autres éléments portant le même nom. Cela garantit qu'une valeur est soumise: true si la case est cochée (en supposant qu'elle se trouve au-dessus de l'élément caché) et false si la case n'est pas cochée (la case vide est ignorée et le caché devient le repli). Le vrai wtf est pourquoi personne d'autre ne l'a signalé.
nerraga
19
@nerraga: Vous vous trompez: c'est du HTML valide d'avoir plusieurs éléments de formulaire avec le même nom; et si c'est le cas, tous les éléments sont publiés, et je ne suis pas sûr que l'ordre soit réellement défini (bien que susceptible d'être simplement dans l'ordre des pages en pratique). Utiliser le mot "false" comme valeur est quelque peu trompeur, alors oui, c'est un WTF - un choix meilleur et moins trompeur aurait été quelque chose comme "existe" ou un jeton sans signification comme un tiret.
Eamon Nerbonne
95

HtmlHelper ajoute une entrée masquée pour informer le contrôleur de l'état non vérifié. Donc, pour avoir le bon statut vérifié:

bool bChecked = form[key].Contains("true");
Andrea Balducci
la source
1
C'est l'approche la plus simple à ce problème et c'est ce que j'utilise toujours. Il vous permet d'utiliser les méthodes d'assistance et de prendre en compte le comportement des MVC ASP.NET.
Nick Daniels
8
.. ou dans le cas de non vérifié: bool bChecked = formulaire [clé] .Equals ("false"); Faire un .Contains ("false") échoue car la vraie valeur est "true, false".
Mark Robinson
54

Si vous vous demandez POURQUOI ils ont mis un champ caché avec le même nom que la case à cocher, la raison est la suivante:

Commentaire du code source MVCBetaSource \ MVC \ src \ MvcFutures \ Mvc \ ButtonsAndLinkExtensions.cs

Rendre un supplément <input type="hidden".../>pour les cases à cocher. Cela résout les scénarios dans lesquels les cases à cocher non cochées ne sont pas envoyées dans la demande. L'envoi d'une entrée masquée permet de savoir que la case à cocher était présente sur la page lors de la soumission de la demande.

Je suppose que dans les coulisses, ils doivent savoir cela pour se lier aux paramètres des méthodes d'action du contrôleur. Vous pourriez alors avoir un booléen à trois états, je suppose (lié à un paramètre booléen nullable). Je ne l'ai pas essayé mais j'espère que c'est ce qu'ils ont fait.

Simon_Weaver
la source
4
Oui, cela est pratique dans les scénarios où vous avez des grilles paginées, etc. et que vous souhaitez désélectionner l'élément qui a été précédemment sélectionné dans un objet métier.
RichardOD
49

Vous devez également utiliser <label for="checkbox1">Checkbox 1</label>car les utilisateurs peuvent alors cliquer sur le texte de l'étiquette ainsi que sur la case à cocher elle-même. Il est également plus facile à coiffer et au moins dans IE, il sera mis en surbrillance lorsque vous parcourez les contrôles de la page.

<%= Html.CheckBox("cbNewColors", true) %><label for="cbNewColors">New colors</label>

Ce n'est pas seulement une chose «oh je pourrais le faire». C'est une amélioration significative de l'expérience utilisateur. Même si tous les utilisateurs ne savent pas qu'ils peuvent cliquer sur l'étiquette, beaucoup le feront.

Simon_Weaver
la source
27

Je suis surpris qu'aucune de ces réponses n'ait utilisé les fonctionnalités MVC intégrées pour cela.

J'ai écrit un article de blog à ce sujet ici , qui relie même les étiquettes à la case à cocher. J'ai utilisé le dossier EditorTemplate pour accomplir cela d'une manière propre et modulaire.

Vous vous retrouverez simplement avec un nouveau fichier dans le dossier EditorTemplate qui ressemble à ceci:

@model SampleObject

@Html.CheckBoxFor(m => m.IsChecked)
@Html.HiddenFor(m => m.Id)
@Html.LabelFor(m => m.IsChecked, Model.Id)

dans votre vue actuelle, il ne sera pas nécessaire de boucler cela, simplement 1 ligne de code:

@Html.EditorFor(x => ViewData.Model)

Visitez mon article de blog pour plus de détails.

Shawn Mclean
la source
1
Je vous remercie! Everboyd utilise toujours les anciennes méthodes de formulaires. Et lorsque vous soumettez, vous pouvez accéder au modèle sans toute cette collection [] et les ordures request.form - c'est ce que je cherchais. +1et + bière
Piotr Kula
2
Cela devrait être la meilleure réponse à la question. Très simple et facile à mettre en œuvre.
Johan Gunawan
Je me suis débattu pendant un certain temps pour trouver une solution élégante à cela avant de trouver cette réponse. C'est vieux de quelques années mais toujours parfaitement valable en 2016! Si vous utilisez dans plusieurs cas, utilisez le SelectListItem intégré comme un type générique qui est parfaitement adapté à cela
Phil
SampleObject est-il une liste? Tels que: @ModelType List (Of Type)
JoshYates1980
25

Voici ce que je fais.

Vue:


<input type="checkbox" name="applyChanges" />

Manette:


var checkBox = Request.Form["applyChanges"];

if (checkBox == "on")
{
...
}

J'ai trouvé que les méthodes d'assistance Html. * N'étaient pas si utiles dans certains cas, et que je ferais mieux de le faire en HTML ancien. Celui-ci étant l'un d'eux, l'autre qui me vient à l'esprit est les boutons radio.

Edit: c'est sur Preview 5, évidemment YMMV entre les versions.

mmacaulay
la source
1
Je veux juste ajouter un autre exemple: Object.Property =! String.IsNullOrEmpty (Request.Form ["NAME"]);
Gup3rSuR4c
si elle n'est pas cochée, elle passe à l'exception
Xulfee
10

Ils semblent choisir de lire uniquement la première valeur, c'est donc "vrai" lorsque la case est cochée et "faux" lorsque seule la valeur cachée est incluse. Ceci est facilement récupéré avec du code comme celui-ci:

model.Property = collection["ElementId"].ToLower().StartsWith("true");
Duveteux
la source
8

@Dylan Beattie Great Find !!! Je vous remercie beaucoup. Pour développer encore plus, cette technique fonctionne également parfaitement avec l'approche View Model. MVC est tellement cool qu'il est assez intelligent pour lier un tableau de Guids à une propriété du même nom que l'objet Model lié à la vue. Exemple:

ViewModel:

public class SampleViewModel
{
    public IList<SampleObject> SampleObjectList { get; set; }
    public Guid[] SelectedObjectIds { get; set; }

    public class SampleObject
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

Vue:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Sample View</h2>
<table>
    <thead> 
        <tr>
            <th>Checked</th>
            <th>Object Name</th>
        </tr>
    </thead> 
<% using (Html.BeginForm()) %>
<%{%>                    
    <tbody>
        <% foreach (var item in Model.SampleObjectList)
           { %>
            <tr>
                <td><input type="checkbox" name="SelectedObjectIds" value="<%= item.Id%>" /></td>
                <td><%= Html.Encode(item.Name)%></td>
            </tr>
        <% } %>
    </tbody>
</table>
<input type="submit" value="Submit" />
<%}%>                    

Manette:

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult SampleView(Guid id)
    {
        //Object to pass any input objects to the View Model Builder 
        BuilderIO viewModelBuilderInput = new BuilderIO();

        //The View Model Builder is a conglomerate of repositories and methods used to Construct a View Model out of Business Objects
        SampleViewModel viewModel = sampleViewModelBuilder.Build(viewModelBuilderInput);

        return View("SampleView", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SampleView(SampleViewModel viewModel)
    {
        // The array of Guids successfully bound to the SelectedObjectIds property of the View Model!
        return View();
    }

Quiconque connaît la philosophie de View Model se réjouira, cela fonctionne comme un champion!

nautic20
la source
Merci, votre SampleViewModel a dissipé une certaine confusion dans la réponse d'origine.
parlement
6

Je voudrais également souligner que vous pouvez attribuer un nom différent à chaque case à cocher et que ce nom fasse partie des paramètres d'actionrésultats.

Exemple,

Vue:

 <%= Html.CheckBox("Rs232CheckBox", false, new { @id = "rs232" })%>RS-232

 <%= Html.CheckBox("Rs422CheckBox", false, new { @id = "rs422" })%>RS-422

Manette:

public ActionResults MyAction(bool Rs232CheckBox, bool Rs422CheckBox) {
    ...
}

Les valeurs de la vue sont transmises à l'action car les noms sont identiques.

Je sais que cette solution n'est pas idéale pour votre projet, mais j'ai pensé jeter l'idée là-bas.

Darcy
la source
5
<input type = "checkbox" name = "checkbox1" /> <label> Check to say hi.</label>

Du contrôleur:

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Index(FormCollection fc)
    {

         var s = fc["checkbox1"];

         if (s == "on")
         {
             string x = "Hi";
         }
    }
bluwater2001
la source
4

Ce problème se produit également dans la version 1.0. Html.Checkbox () provoque l'ajout d'un autre champ masqué avec le même nom / id que votre case à cocher d'origine. Et comme j'essayais de charger un tableau de cases à cocher à l'aide de document.GetElemtentsByName (), vous pouvez deviner comment les choses se gâtaient. C'est bizarre.

Farhan Zia
la source
4

D'après ce que je peux comprendre, le modèle ne veut pas deviner si vérifié = vrai ou faux, j'ai contourné cela en définissant un attribut de valeur sur l'élément de case à cocher avec jQuery avant de soumettre le formulaire comme ceci:

 $('input[type="checkbox"]').each(function () {
       $(this).attr('value', $(this).is(':checked'));
 }); 

De cette façon, vous n'avez pas besoin d'un élément caché juste pour stocker la valeur de la case à cocher.

BraveNewMath
la source
4

Je sais que cette question a été écrite lorsque MVC3 n'était pas sorti, mais pour quiconque vient à cette question et utilise MVC3, vous voudrez peut-être la manière "correcte" de le faire.

Bien que je pense que faire le tout

Contains("true");

chose est géniale et propre, et fonctionne sur toutes les versions MVC, le problème est qu'il ne prend pas en compte la culture (comme si c'était vraiment important dans le cas d'un bool).

La manière "correcte" de déterminer la valeur d'un booléen, au moins dans MVC3, consiste à utiliser ValueProvider.

var value = (bool)ValueProvider.GetValue("key").ConvertTo(typeof(bool));

Je le fais dans l'un des sites de mon client lorsque je modifie les autorisations:

var allPermissionsBase = Request.Params.AllKeys.Where(x => x.Contains("permission_")).ToList();
var allPermissions = new List<KeyValuePair<int, bool>>();

foreach (var key in allPermissionsBase)
{
     // Try to parse the key as int
     int keyAsInt;
     int.TryParse(key.Replace("permission_", ""), out keyAsInt);

     // Try to get the value as bool
     var value = (bool)ValueProvider.GetValue(key).ConvertTo(typeof(bool));
}

Maintenant, la beauté de ceci est que vous pouvez l'utiliser avec à peu près n'importe quel type simple, et il sera même correct en fonction de la culture (pensez à l'argent, décimales, etc.).

Le ValueProvider est ce qui est utilisé lorsque vous formez vos actions comme ceci:

public ActionResult UpdatePermissions(bool permission_1, bool permission_2)

mais lorsque vous essayez de créer dynamiquement ces listes et de vérifier les valeurs, vous ne connaîtrez jamais l'ID au moment de la compilation, vous devez donc les traiter à la volée.

Dan VanWinkle
la source
y a-t-il une raison pour laquelle vous utilisez Request.Params au lieu d'ajouter un paramètre FormCollection à la méthode?
SwissCoder
Aucune raison, je ne vois vraiment aucun avantage dans un sens ou dans l'autre.
Dan VanWinkle
1
Désolé, il y a en fait une raison. Je viens de regarder mon code, et j'utilise cette méthode pour 5 appels différents, et je n'avais pas envie de transmettre FormCollection à partir de chaque méthode. C'était le moyen le plus simple d'obtenir les clés sans avoir à passer quoi que ce soit.
Dan VanWinkle
ouais je suis nouveau sur MVC. Et j'ai lu quelque part qu'il est préférable en général d'utiliser le FormCollection au lieu de Request.Params, de la même manière qu'en utilisant un modèle typé au lieu du FormCollection (il serait donc préférable d'utiliser un modèle et d'éviter d'utiliser Request.Params en général). J'ai remarqué que d'autres choses du code ne sont pas utilisées, comme la variable allPermissions et la keyAsInt. Merci de répondre.
SwissCoder
3

La façon la plus simple de le faire est donc ...

Vous définissez le nom et la valeur.

<input type="checkbox" name="selectedProducts" value="@item.ProductId" />@item.Name

Ensuite, lors de la soumission, saisissez les valeurs des cases à cocher et enregistrez-les dans un tableau int. puis la fonction LinQ appropriée. C'est tout..

[HttpPost]
        public ActionResult Checkbox(int[] selectedObjects)
        {
            var selected = from x in selectedObjects
                           from y in db
                           where y.ObjectId == x
                           select y;                   

            return View(selected);
        }
kk-dev11
la source
3

Identique à la réponse de nautic20, utilisez simplement la liste de cases à cocher de liaison de modèle par défaut MVC avec le même nom qu'une propriété de collection de string / int / enum dans ViewModel. C'est ça.

Mais un problème doit être souligné. Dans chaque composant de case à cocher, vous ne devez pas y mettre "Id" qui affectera la liaison du modèle MVC.

Le code suivant fonctionnera pour la liaison de modèle:

 <% foreach (var item in Model.SampleObjectList)
       { %>
        <tr>
            <td><input type="checkbox" name="SelectedObjectIds" value="<%= item.Id%>" /></td>
            <td><%= Html.Encode(item.Name)%></td>
        </tr>
 <% } %>

Les codes suivants ne se lieront pas au modèle (la différence ici est l'ID attribué pour chaque case à cocher)

<% foreach (var item in Model.SampleObjectList)
       { %>
        <tr>
            <td><input type="checkbox" name="SelectedObjectIds" id="[some unique key]" value="<%= item.Id%>" /></td>
            <td><%= Html.Encode(item.Name)%></td>
        </tr>
<% } %>
ChinaHelloWorld
la source
Merci de répondre, mais cette question est juuuust un peu vieux. Six ans, environ. Vous voudrez peut-être modifier et spécifier la version de MVC que vous utilisez. Cela aidera toute personne utilisant une version plus récente à trouver cette réponse.
Je suis d'accord. La suppression de l'ID unique a résolu ce problème après de nombreux maux de tête et des grincements de dents
Ody
La version de MVC que j'utilise est .net MVC 4.0 pour ce problème.
ChinaHelloWorld
2

c'est ce que j'ai fait pour perdre les valeurs doubles lors de l'utilisation du Html.CheckBox (...

Replace("true,false","true").Split(',')

avec 4 cases cochées, non cochées, non cochées, cochées cela devient vrai, faux, faux, faux, vrai, faux en vrai, faux, faux, vrai. juste ce dont j'avais besoin

Jeroen
la source
0

Que diriez-vous quelque chose comme ça?

bool isChecked = false;
if (Boolean.TryParse(Request.Form.GetValues(”chkHuman”)[0], out isChecked) == false)
    ModelState.AddModelError(”chkHuman”, Nice try.”);
Skadoosh
la source
0

Lorsque j'utilise la case à cocher HtmlHelper, je préfère de loin travailler avec les données du formulaire de case à cocher publiées sous forme de tableau. Je ne sais pas vraiment pourquoi, je sais que les autres méthodes fonctionnent, mais je pense que je préfère simplement traiter les chaînes séparées par des virgules comme un tableau autant que possible.

Faire un test «vérifié» ou vrai serait donc:

//looking for [true],[false]
bool isChecked = form.GetValues(key).Contains("true"); 

Faire un faux chèque serait:

//looking for [false],[false] or [false]
bool isNotChecked = !form.GetValues(key).Contains("true"); 

La principale différence est d'utiliser GetValuescar cela retourne comme un tableau.

eyesnz
la source
0

Faites-le sur $(document).ready:

$('input:hidden').each(function(el) {
    var that = $(this)[0];
    if(that.id.length < 1 ) {

        console.log(that.id);
        that.parentElement.removeChild(that);

    }
});
doronAv
la source
2
Malheureusement, vous pourriez obtenir des résultats étranges côté serveur pour les personnes avec JavaScript désactivé (plug-in NoScript, vieux téléphones mobiles, Lynx ...)
ArIck
0

Ma solution est:

<input type="checkbox"  id="IsNew-checkbox"  checked="checked" />     
<input type="hidden"  id="IsNew" name="IsNew" value="true"  />      
<script language="javascript" type="text/javascript" >     
  $('#IsNew-checkbox').click(function () {    
      if ($('#IsNew-checkbox').is(':checked')) {    
          $('#IsNew').val('true');    
      } else {    
          $('#IsNew').val('false');    
       }    
  });   
</script>  

Plus vous pouvez trouver ici: http://www.blog.mieten.pl/2010/12/asp-net-mvc-custom-checkbox-as-solution-of-string-was-not-recognized-as-a- valid-boolean /

pawlom84
la source
Vous n'avez pas besoin d'utiliser de Javascript pour cela ... var isChecked = formData [key] .Contains ("true");
Jammer
0

J'ai eu presque le même problème mais la valeur de retour de mon contrôleur a été bloquée avec d'autres valeurs.

J'ai trouvé une solution simple mais cela semble un peu difficile.

Essayez de taper Viewbag.votre contrôleur et maintenant vous lui donnez un nom commeViewbag.Checkbool

Passez maintenant à la vue et essayez @Viewbag.Checkboolavec cela, vous obtiendrez la valeur du contrôleur.

Mes paramètres de contrôleur ressemblent à ceci:

public ActionResult Anzeigen(int productid = 90, bool islive = true)

et ma case à cocher se mettra à jour comme ceci:

<input id="isLive" type="checkbox" checked="@ViewBag.Value" ONCLICK="window.location.href = '/MixCategory/Anzeigen?isLive=' + isLive.checked.toString()" />
treborian
la source
0

En utilisant @mmacaulay, j'ai trouvé ceci pour bool:

// MVC Work around for checkboxes.
bool active = (Request.Form["active"] == "on");

Si coché actif = vrai

Si non coché actif = faux

Ravi Ram
la source