web-dev-qa-db-fra.com

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

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 de SQL Server. Je souhaite que la liste soit masquée dans la vue et transmise à l'action POST). Plus tard, je souhaiterai peut-être ajouter d'autres éléments à cette liste avec jQuery, ce qui rendra un tableau inutilisable ultérieurement. 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, tout le monde sait pourquoi MVC se comporte comme ça?

87
Anton Smith

Je viens de rencontrer ce problème et de le résoudre 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 cachées dans la liste. Cela semble être le moyen le plus simple de résoudre ce problème.

137
Daniel Mackay

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

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

28
Erik Funkenbusch

C'est un peu un bidouillage, mais si @Html.EditorFor Ou @Html.DisplayFor Fonctionne pour votre liste, si vous voulez vous assurer qu'il est envoyé sur la demande de publication mais qu'il ne soit pas visible, vous pouvez simplement le définir en utilisant display: none; Pour le cacher à la place, par exemple:

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

Que diriez-vous d’utiliser Newtonsoft pour désérialiser l’objet dans une chaîne json, puis l’insérer dans votre champ masqué, par exemple. (Model.DataResponse.Entity.Commission est une liste de simples "CommissionRange" objets comme vous 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]
   }

Rendus 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 choses JS pour éditer le json dans le champ caché avant de poster en retour

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

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

Html.HiddenFor est conçu pour une seule valeur. Vous devrez sérialiser votre liste avant de créer le champ masqué.

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

6
Kyle Trauberman

Je viens de découvrir (après quelques heures d’essai de comprendre pourquoi les valeurs de modèle ne revenaient pas au contrôleur) que caché pour devrait suivre le 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 contenant 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 cela volonté ......

            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>
            }
4
AntDC

Vous pouvez jeter un oeil sur ceci solution .

Mettez seulement HiddenFor dans le EditorTemplate.

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

Cela devrait fonctionner.

3
Wilfart Benjamin

J'ai commencé à fouiller dans le code source de HiddenFor, et je pense que le problème que vous voyez est que votre objet complexe MyList n'est pas implicitement convertible en type string. framework traite votre valeur Model comme null et rend l'attribut value vide.

3
Cᴏʀʏ

Face au même problème. Sans for loop, il n'a posté que le premier élément de la liste. Après avoir parcouru en boucle, 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])
      }
    }
3
Keerthi

Une autre option serait:

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

Un autre moyen de résoudre ce problème consiste à attribuer un ID à chaque objet de votre liste, puis à utiliser @Html.DropDownListFor(model => model.IDs) et à renseigner un tableau contenant les ID.

1
deckeresq

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

Alors la voici:

/// <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)
1
Gh61

La boucle foreach au lieu d'une boucle for pourrait être une solution légèrement plus propre.

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