@ Html.HiddenFor funktioniert nicht mit Listen in ASP.NET MVC

97

Ich verwende ein Modell, das eine Liste als Eigenschaft enthält. Ich fülle diese Liste mit Elementen, die ich von SQL Server greife. Ich möchte, dass die Liste in der Ansicht ausgeblendet und an die POST-Aktion übergeben wird. Später möchte ich vielleicht mit jQuery weitere Elemente zu dieser Liste hinzufügen, wodurch ein Array für eine spätere Erweiterung ungeeignet wird. Normalerweise würden Sie verwenden

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

Um diese Funktionalität zu erreichen, ist die Liste im POST aus irgendeinem Grund immer null.

Sehr einfache Frage, weiß jemand, warum sich MVC so verhält?

Anton Smith
quelle
1
Normalerweise würden Sie nicht ganze Listen so verstecken. Was ist Ihre gewünschte Ausgabe in Bezug auf <input />s?
21.
1
was MyListenthält? HiddenForwird jeweils nur für einen Eingang verwendet.
Daniel A. White
1
Welcher Typ ist Model.MyList? Möglicherweise müssen Sie Ihre Liste manuell serialisieren / deserialisieren.
Kyle Trauberman
1
[ stackoverflow.com/questions/4381871/… Ähnliche Frage.
Sanjeevi Subramani
1
Ähnliche Frage: Verwendung von HiddenFor mit Intellisense
Sanjeevi Subramani

Antworten:

161

Ich bin gerade auf dieses Problem gestoßen und habe es einfach folgendermaßen gelöst:

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

Wenn Sie ein for anstelle eines foreach verwenden, funktioniert die Modellbindung ordnungsgemäß und nimmt alle Ihre versteckten Werte in der Liste auf. Scheint der einfachste Weg zu sein, um dieses Problem zu lösen.

Daniel Mackay
quelle
5
Vielen Dank! hat meine Nacht gerettet.
TSmith
7
Danke - schöne einfache Lösung. Es wird jedoch nur ein winziger Mod benötigt: Auf das ID-Feld des Objekts muss verwiesen werden. Wenn das Feld also RowId heißt, dann:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta
3
arbeitete für mich, auch wenn ich mehrere Felder auf den Modellen in der Sammlung hatte. Dh beim nächsten Mal @Html.EditorFor(model => Model.ToGroups[i].Id)gefolgt @Html.EditorFor(model => Model.ToGroups[i].Description)- beide in der for-Schleife. Und der Controller konnte es einer Liste der Modelle mit diesen Feldern zuordnen. Und um sicherzustellen, dass nichts davon auf dem Bildschirm angezeigt wird, umgeben Sie es einfach in<div style="display: none;"></div>
Don Cheadle
Brillant! Schön gemacht. Hat für mich gearbeitet!
AxleWack
3
@ user3186023 Beantworten eines wirklich alten Kommentars hier, aber vielleicht hat jemand anderes das gleiche Problem: Ändern Sie die- forSchleife in diese:for(int i = 0; i < Model.Departments.Count(); i++)
Stian
28

HiddenFor ist nicht wie DisplayFor oder EditorFor. Es funktioniert nicht mit Sammlungen, sondern nur mit einzelnen Werten.

Sie können den im MVC Futures-Projekt verfügbaren Serialize HTML-Helfer verwenden, um ein Objekt in ein ausgeblendetes Feld zu serialisieren, oder Sie müssen den Code selbst schreiben. Eine bessere Lösung besteht darin, einfach eine ID zu serialisieren und die Daten beim Postback erneut aus der Datenbank abzurufen.

Erik Funkenbusch
quelle
Hast du ein beispiel Ich habe dies versucht und es konnte keine Verbindung zum ViewModel-Wert hergestellt werden, als das Formular gesendet wurde.
Alan Macdonald
@AlanMacdonald - Wenn etwas nicht gebunden werden kann, liegt dies daran, dass Ihre Benennung nicht korrekt ist, höchstwahrscheinlich daran, dass Sie ein foreach anstelle eines for with indexer verwendet haben. Oder vielleicht haben Sie nicht die richtigen Attribute in der Bindung verwendet. Siehe weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch
Vielen Dank. Als ich es versuchte, war es buchstäblich @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs), wo Model mein ViewModel war und eine ModelIDs int-Array-Eigenschaft hatte. Es gab also keine Schleifen oder so. Beim Senden des Formulars waren die ModelIDs im gebundenen ViewModel immer null.
Alan Macdonald
@AlanMacdonald - Sie geben "Model" nicht in den Namen ein.
Erik Funkenbusch
16

Es ist ein bisschen wie ein Hack, aber wenn @Html.EditorForoder @Html.DisplayForarbeiten Sie für Ihre Liste, wenn Sie sicherstellen möchten, dass sie auf der Post-Anfrage gesendet, aber nicht sichtbar ist, können Sie sie einfach so gestalten, dass display: none;sie stattdessen ausgeblendet wird, z.

<div style="display: none;">@Html.EditorFor(model => model.MyList)</div>
Mark Rhodes
quelle
Dadurch wird der Wert im Modell nach der Anforderung nicht gespeichert.
nldev
Wenn .EditorFor so eingerichtet ist, dass es richtig funktioniert, sollte dies meiner Meinung nach auch funktionieren.
Mark Rhodes
9

Wie wäre es mit Newtonsoft, um das Objekt in eine JSON-Zeichenfolge zu deserialisieren und diese dann in Ihr ausgeblendetes Feld einzufügen, z. B. ( Model.DataResponse.Entity.Commission ist eine Liste einfacher "CommissionRange" -Objekte, wie Sie im JSON sehen werden).

@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]
   }

Rendert als:

<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}]">

In meinem Fall mache ich einige JS-Sachen, um den JSON im versteckten Feld zu bearbeiten, bevor ich ihn zurückschicke

In meinem Controller verwende ich dann erneut Newtonsoft, um Folgendes zu deserialisieren:

string jsonCommissionRange = Request.Form["DataResponse_Entity_Commission"];
List<CommissionRange> commissionRange = JsonConvert.DeserializeObject<List<CommissionRange>>(jsonCommissionRange);
Adam Hey
quelle
Das hat bei mir funktioniert. Ich fand es viel sauberer, dass die Lösung akzeptiert wurde.
E-on
6

Html.HiddenForist nur für einen Wert ausgelegt. Sie müssen Ihre Liste auf irgendeine Weise serialisieren, bevor Sie das ausgeblendete Feld erstellen.

Wenn Ihre Liste beispielsweise vom Typ Zeichenfolge ist, können Sie die Liste zu einer durch Kommas getrennten Liste zusammenfügen und die Liste nach dem Zurücksenden in Ihrem Controller aufteilen.

Kyle Trauberman
quelle
4

Ich habe gerade herausgefunden (nach ein paar Stunden, in denen versucht wurde herauszufinden, warum Modellwerte nicht zum Controller zurückkehren), dass versteckt für den EditorFor folgen sollte.

Wenn ich nichts anderes falsch mache, habe ich das gefunden. Ich werde den Fehler nicht noch einmal machen.

Im Kontext eines Modells, das eine Liste einer anderen Klasse enthält.

Dies wird NICHT funktionieren:

        @{
            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>
            }
        }

Wo wie das wird ......

            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
quelle
3

Ich habe angefangen, den Quellcode nach zu durchsuchen HiddenFor, und ich denke, die Straßensperre, die Sie sehen, ist, dass Ihr komplexes Objekt MyListnicht implizit in Typ konvertierbar ist. Daher stringbehandelt das Framework Ihren ModelWert als nullund macht das valueAttribut leer.

Cᴏʀᴏʀ
quelle
3

Sie können sich diese Lösung ansehen .

Fügen Sie nur HiddenFor in die EditorTemplate ein.

Und aus Ihrer Sicht setzen Sie dies: @Html.EditorFor(model => model.MyList)

Es sollte funktionieren.

Wilfart Benjamin
quelle
3

Konnte das gleiche Problem. Ohne for-Schleife wurde nur das erste Element der Liste veröffentlicht. Nach dem Durchlaufen der for-Schleife kann die vollständige Liste beibehalten und erfolgreich veröffentlicht werden.

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

Eine andere Option wäre:

<input type="hidden" value=@(string.Join(",", Model.MyList)) />
TiagoBrenck
quelle
Dies war auch meine erste Idee. Ich hatte jedoch ein Ansichtsmodell, das erwartete, dass ein int [] für das MyList-Feld und eine durch Kommas getrennte Zeichenfolge vom MVC-Bindungsmechanismus nicht in ein Array analysiert werden.
Tadej Mali
2

Die foreachSchleife anstelle einer forSchleife könnte eine etwas sauberere Lösung sein.

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

Eine andere Möglichkeit, dies zu beheben, besteht darin, jedem Objekt in Ihrer Liste eine ID zu geben und dann @Html.DropDownListFor(model => model.IDs)ein Array zu verwenden und zu füllen, das die IDs enthält.

deckeresq
quelle
1

Vielleicht zu spät, aber ich habe eine Erweiterungsmethode für versteckte Felder aus der Sammlung erstellt (mit einfachen Datentypelementen):

Hier ist es also:

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

Die Verwendung ist so einfach wie:

@Html.HiddenForCollection(m => m.MyList)
Gh61
quelle