@ 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?

Anton Smith
la source
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
1
[ stackoverflow.com/questions/4381871/... Question similaire.
Sanjeevi Subramani
1
Question similaire: Utilisation de HiddenFor avec intellisense
Sanjeevi Subramani

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.

Daniel Mackay
la source
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.

Erik Funkenbusch
la source
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>
Mark Rhodes
la source
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);
Adam Hey
la source
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.

Kyle Trauberman
la source
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>
            }
AntDC
la source
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.

Cᴏʀʏ
la source
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.

Wilfart Benjamin
la source
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])
      }
    }
Keerthi
la source
2

Une autre option serait:

<input type="hidden" value=@(string.Join(",", Model.MyList)) />
TiagoBrenck
la source
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)
}
Sébastien
la source
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.

deckeresq
la source
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)
Gh61
la source