@ Html.HiddenFor ne fonctionne pas sur les listes dans ASP.NET MVC


97

J'utilise un modèle qui contient une liste en tant que propriété. Je remplis cette liste avec des éléments que je récupère sur SQL Server. Je veux que la liste soit masquée dans la vue et transmise à l'action POST. Plus tard, je souhaiterai peut-être ajouter plus d'éléments à cette liste avec jQuery, ce qui rend un tableau impropre à une expansion ultérieure. Normalement, vous utiliseriez

@Html.HiddenFor(model => model.MyList)

pour accomplir cette fonctionnalité, mais pour une raison quelconque, la liste dans POST est toujours nulle.

Question très simple, quelqu'un sait pourquoi MVC se comporte comme ça?


1
Normalement, vous ne cacheriez pas des listes entières comme ça. Quelle est votre sortie souhaitée en termes de <input />s?
Cᴏʀʏ

1
que MyListcontient HiddenForn'est utilisé que pour une entrée à la fois.
Daniel A. White

1
Quel type est Model.MyList? Vous devrez peut-être effectuer manuellement une sérialisation / désérialisation de votre liste.
Kyle Trauberman


Réponses:


161

Je viens de rencontrer ce problème et je l'ai résolu simplement en procédant comme suit:

@for(int i = 0; i < Model.ToGroups.Length; i++)
{
    @Html.HiddenFor(model => Model.ToGroups[i])
}

En utilisant un for au lieu d'un foreach, la liaison de modèle fonctionnera correctement et récupérera toutes vos valeurs masquées dans la liste. Cela semble être le moyen le plus simple de résoudre ce problème.


5
Merci! sauvé ma nuit.
TSmith

7
Merci - belle solution simple. Juste un petit mod nécessaire cependant: le champ Id de l'objet doit être référencé. Donc, si le champ s'appelle RowId, alors:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta

3
a travaillé pour moi, même lorsque j'avais plusieurs champs sur les modèles de la collection. Ie @Html.EditorFor(model => Model.ToGroups[i].Id)suivi de @Html.EditorFor(model => Model.ToGroups[i].Description)la prochaine fois - à la fois dans la boucle for. Et le contrôleur a pu le mapper à une liste des modèles avec ces champs. Et pour vous assurer que rien de tout cela n'apparaît à l'écran, entourez-le simplement de<div style="display: none;"></div>
Don Cheadle

Brillant! Bien fait. A travaillé pour moi!
AxleWack

3
@ user3186023 Répondre à un très vieux commentaire ici, mais peut-être que quelqu'un d'autre aura le même problème: Changez la forboucle en ceci:for(int i = 0; i < Model.Departments.Count(); i++)
Stian

28

HiddenFor n'est pas comme un DisplayFor ou un EditorFor. Cela ne fonctionnera pas avec des collections, seulement des valeurs uniques.

Vous pouvez utiliser l'assistant Serialize HTML disponible dans le projet MVC Futures pour sérialiser un objet dans un champ masqué, ou vous devrez écrire le code vous-même. Une meilleure solution consiste simplement à sérialiser un identifiant quelconque et à récupérer les données de la base de données lors de la publication.


Avez-vous un exemple? J'ai essayé cela et il n'a pas réussi à se lier à la valeur ViewModel lorsque le formulaire a été soumis.
Alan Macdonald

@AlanMacdonald - si quelque chose ne parvient pas à se lier, c'est parce que votre dénomination n'est pas correcte, plus que probablement parce que vous avez utilisé un foreach au lieu d'un for avec indexer. Ou peut-être n'avez-vous pas utilisé les attributs appropriés dans la liaison. Voir weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch

Merci. En fait, quand j'ai essayé, c'était littéralement @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs) où Model était mon ViewModel et il avait une propriété de tableau ModelIDs int. Il n'y avait donc pas de boucles ou quoi que ce soit. Lorsque le formulaire a été soumis, les ModelID étaient toujours nuls dans le ViewModel lié.
Alan Macdonald

@AlanMacdonald - Vous n'incluez pas "Modèle" dans le nom.
Erik Funkenbusch

16

Il est un peu un hack, mais si @Html.EditorForou le @Html.DisplayFortravail de votre liste, si vous voulez vous assurer qu'il est envoyé à la demande de poste , mais pas visible, vous pouvez simplement le style à l' aide display: none;de le cacher au lieu, par exemple:

<div style="display: none;">@Html.EditorFor(model => model.MyList)</div>

Cela ne sauvegarde pas la valeur dans le modèle lors de la publication de la demande.
nldev

Si .EditorFor est configuré pour fonctionner correctement, cela devrait également fonctionner, je crois.
Mark Rhodes

9

Qu'en est-il de l'utilisation de Newtonsoft pour désérialiser l'objet dans une chaîne json, puis l'insérer dans votre champ caché, par exemple ( Model.DataResponse.Entity.Commission est une liste d' objets "CommissionRange" simples comme vous le verrez dans le JSON)

@using (Ajax.BeginForm("Settings", "AffiliateProgram", Model.DataResponse, new AjaxOptions { UpdateTargetId = "result" }))
   {
      string commissionJson = JsonConvert.SerializeObject(Model.DataResponse.Entity.Commission);
      @Html.HiddenFor(data => data.DataResponse.Entity.Guid)
      @Html.Hidden("DataResponse_Entity_Commission", commissionJson)
      [Rest of my form]
   }

Rend comme:

<input id="DataResponse_Entity_Commission" name="DataResponse_Entity_Commission" type="hidden" value="[{"RangeStart":0,"RangeEnd":0,"CommissionPercent":2.00000},{"RangeStart":1,"RangeEnd":2,"CommissionPercent":3.00000},{"RangeStart":2,"RangeEnd":0,"CommissionPercent":2.00000},{"RangeStart":3,"RangeEnd":2,"CommissionPercent":1.00000},{"RangeStart":15,"RangeEnd":10,"CommissionPercent":5.00000}]">

Dans mon cas, je fais des trucs JS pour éditer le json dans le champ caché avant de le renvoyer

Dans mon contrôleur, j'utilise à nouveau Newtonsoft pour désérialiser:

string jsonCommissionRange = Request.Form["DataResponse_Entity_Commission"];
List<CommissionRange> commissionRange = JsonConvert.DeserializeObject<List<CommissionRange>>(jsonCommissionRange);

Cela a fonctionné pour moi. Je pensais que c'était beaucoup plus propre que la solution acceptée.
e-le

6

Html.HiddenForest conçu pour une seule valeur. Vous devrez sérialiser votre liste d'une manière ou d'une autre avant de créer le champ masqué.

Par exemple, si votre liste est de type chaîne, vous pouvez joindre la liste dans une liste séparée par des virgules, puis diviser la liste après la publication dans votre contrôleur.


4

Je viens de découvrir (après quelques heures à essayer de comprendre pourquoi les valeurs du modèle ne retournaient pas au contrôleur) que masqué pour devrait suivre EditorFor.

À moins que je ne fasse autre chose de mal, c'est ce que j'ai trouvé. Je ne referai plus l'erreur.

Dans le contexte d'un modèle qui contient une liste d'une autre classe.

Cela ne fonctionnera PAS:

        @{
            for (int i = 0; i < Model.Categories.Count; i++)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(modelItem => Model.Categories[i].Id)
                        @Html.HiddenFor(modelItem => Model.Categories[i].ProductCategoryId)
                        @Html.HiddenFor(modelItem => Model.Categories[i].CategoryName)                            
                        @Html.DisplayFor(modelItem => Model.Categories[i].CategoryName)                            
                    </td>
                    <td>
                        @Html.HiddenFor(modelItem => Model.Categories[i].DailyPurchaseLimit)                                                        
                        @Html.EditorFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                        @Html.ValidationMessageFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                    </td>
                    <td style="text-align: center">
                        @Html.HiddenFor(modelItem => Model.Categories[i].IsSelected)                            
                        @Html.EditorFor(modelItem => Model.Categories[i].IsSelected)
                    </td>
                </tr>
            }
        }

Où comme ça va ......

            for (int i = 0; i < Model.Categories.Count; i++)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(modelItem => Model.Categories[i].Id)
                        @Html.HiddenFor(modelItem => Model.Categories[i].ProductCategoryId)
                        @Html.HiddenFor(modelItem => Model.Categories[i].CategoryName)                            
                        @Html.DisplayFor(modelItem => Model.Categories[i].CategoryName)                            
                    </td>
                    <td>
                        @Html.EditorFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                        @Html.HiddenFor(modelItem => Model.Categories[i].DailyPurchaseLimit)                            
                        @Html.ValidationMessageFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                    </td>
                    <td style="text-align: center">
                        @Html.EditorFor(modelItem => Model.Categories[i].IsSelected)
                        @Html.HiddenFor(modelItem => Model.Categories[i].IsSelected)                            
                    </td>
                </tr>
            }

3

J'ai commencé à fouiller dans le code source de HiddenFor, et je pense que l'obstacle que vous voyez est que votre objet complexe MyListn'est pas implicitement convertible en type string, donc le cadre traite votre Modelvaleur comme nullet rend l' valueattribut vide.


3

Vous pouvez jeter un oeil sur cette solution .

Mettez uniquement HiddenFor dans EditorTemplate.

Et dans votre vue, mettez ceci: @Html.EditorFor(model => model.MyList)

Cela devrait fonctionner.


3

Face au même problème. Sans boucle for, il n'a posté que le premier élément de la liste. Après avoir parcouru la boucle for, il peut conserver la liste complète et publier avec succès.

 @if (Model.MyList!= null)
    {
    for (int i = 0; i < Model.MyList.Count; i++)
      {
        @Html.HiddenFor(x => x.MyList[i])
      }
    }

2

Une autre option serait:

<input type="hidden" value=@(string.Join(",", Model.MyList)) />

C'était aussi ma première idée. Mais j'avais un modèle de vue, qui attendait un int [] pour le champ MyList et la chaîne séparée par des virgules n'est pas analysée dans un tableau par le mécanisme de liaison MVC.
Tadej Mali

2

La foreachboucle au lieu d'une forboucle pourrait être une solution légèrement plus propre.

@foreach(var item in Model.ToGroups)
{
    @Html.HiddenFor(model => item)
}

1

Une autre façon possible de résoudre ce problème serait de donner à chaque objet de votre liste un ID, puis d'utiliser @Html.DropDownListFor(model => model.IDs)et de remplir un tableau contenant les ID.


1

peut-être en retard, mais j'ai créé une méthode d'extension pour les champs cachés de la collection (avec des éléments de type de données simples):

Alors voilà:

/// <summary>
/// Returns an HTML hidden input element for each item in the object's property (collection) that is represented by the specified expression.
/// </summary>
public static IHtmlString HiddenForCollection<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) where TProperty : ICollection
{
    var model = html.ViewData.Model;
    var property = model != null
                ? expression.Compile().Invoke(model)
                : default(TProperty);

    var result = new StringBuilder();
    if (property != null && property.Count > 0)
    {
        for(int i = 0; i < property.Count; i++)
        {
            var modelExp = expression.Parameters.First();
            var propertyExp = expression.Body;
            var itemExp = Expression.ArrayIndex(propertyExp, Expression.Constant(i));

            var itemExpression = Expression.Lambda<Func<TModel, object>>(itemExp, modelExp);

            result.AppendLine(html.HiddenFor(itemExpression).ToString());
        }
    }

    return new MvcHtmlString(result.ToString());
}

L'utilisation est aussi simple que:

@Html.HiddenForCollection(m => m.MyList)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.