@ Html.HiddenFor nie działa na listach w ASP.NET MVC


97

Używam modelu, który zawiera listę jako właściwość. Zapełniam tę listę elementami pobranymi z SQL Server. Chcę, aby lista była ukryta w widoku i przekazana do akcji POST. Później mogę chcieć dodać więcej elementów do tej listy za pomocą jQuery, co sprawia, że ​​tablica nie nadaje się do późniejszego rozszerzenia. Zwykle używałbyś

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

aby zrealizować tę funkcję, ale z jakiegoś powodu lista w POST jest zawsze pusta.

Bardzo proste pytanie, czy ktoś wie, dlaczego MVC tak się zachowuje?


1
Zwykle nie ukrywałbyś tak całych list. Jaki jest twój pożądany wynik w zakresie <input />s?
Cᴏʀʏ

1
co MyListzawiera? HiddenForjest używany tylko dla jednego wejścia naraz.
Daniel A. White,

1
Jaki jest typ Model.MyList? Może być konieczne ręczne wykonanie serializacji / deserializacji na liście.
Kyle Trauberman,


1

Odpowiedzi:


161

Właśnie natknąłem się na ten problem i rozwiązałem go, wykonując następujące czynności:

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

Używając for zamiast foreach, powiązanie modelu będzie działać poprawnie i pobierze wszystkie ukryte wartości z listy. Wydaje się, że jest to najprostszy sposób rozwiązania tego problemu.


5
Dzięki! uratował mi noc.
TSmith,

7
Dzięki - fajne proste rozwiązanie. Potrzebny był jednak tylko mały mod: pole Id obiektu musi być odniesione. Więc jeśli pole nazywa się RowId, to:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta

3
pracował dla mnie, nawet gdy miałem wiele pól na modelach w kolekcji. Tj @Html.EditorFor(model => Model.ToGroups[i].Id)następnie @Html.EditorFor(model => Model.ToGroups[i].Description)przy następnym - zarówno w pętli for. Kontroler był w stanie odwzorować to na listę modeli z tymi polami. Aby upewnić się, że żaden z nich nie pojawi się na ekranie, po prostu otocz go<div style="display: none;"></div>
Don Cheadle

Znakomity! Ładnie wykonane. Pracował dla mnie!
AxleWack

3
@ user3186023 Odpowiadasz na naprawdę stary komentarz, ale może ktoś inny będzie miał ten sam problem: Zmień for-loop na to:for(int i = 0; i < Model.Departments.Count(); i++)
Stian

28

HiddenFor nie jest jak DisplayFor lub EditorFor. Nie będzie działać z kolekcjami, tylko z pojedynczymi wartościami.

Możesz użyć pomocnika Serialize HTML dostępnego w projekcie MVC Futures do serializacji obiektu do ukrytego pola lub będziesz musiał sam napisać kod. Lepszym rozwiązaniem jest po prostu serializacja jakiegoś identyfikatora i ponowne pobranie danych z bazy danych po ogłoszeniu zwrotnym.


Masz przykład? Próbowałem tego i nie udało mi się powiązać z wartością ViewModel podczas przesyłania formularza.
Alan Macdonald

@AlanMacdonald - jeśli coś się nie wiąże, to dlatego, że nazewnictwo jest nieprawidłowe, najprawdopodobniej dlatego, że użyłeś foreach zamiast for z indeksatorem. A może nie użyłeś odpowiednich atrybutów w powiązaniu. Zobacz weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch

Dzięki. Właściwie, kiedy próbowałem, było to dosłownie @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs), gdzie Model był moim ViewModel i miał właściwość int array ModelIDs. Więc nie było żadnych pętli ani nic. Po przesłaniu formularza ModelIDs zawsze miały wartość null w powiązanym ViewModel.
Alan Macdonald

@AlanMacdonald - W nazwie nie ma słowa „Model”.
Erik Funkenbusch

16

To trochę hack, ale jeśli @Html.EditorForlub @Html.DisplayForpracujesz na twojej liście, jeśli chcesz się upewnić, że jest wysłana w żądaniu postu, ale nie jest widoczna, możesz po prostu zmienić jej styl na użycie, display: none;aby ją ukryć, np .:

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

Nie zapisuje to wartości w modelu na wysłaniu zapytania.
nldev

Jeśli .EditorFor jest skonfigurowany do prawidłowego działania, to też powinno działać.
Mark Rhodes,

9

A co z używaniem Newtonsoft do deserializacji obiektu do łańcucha json, a następnie wstaw go do swojego pola ukrytego, np. ( Model.DataResponse.Entity.Commission to lista prostych obiektów „CommissionRange”, jak zobaczysz w 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]
   }

Renderuje jako:

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

W moim przypadku robię trochę rzeczy JS, aby edytować json w ukrytym polu przed wysłaniem z powrotem

W moim kontrolerze ponownie używam Newtonsoft do deserializacji:

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

To zadziałało dla mnie. Myślałem, że to dużo czystsze niż przyjęte rozwiązanie.
e-w dniu

6

Html.HiddenForjest przeznaczony tylko dla jednej wartości. Będziesz musiał w jakiś sposób serializować swoją listę przed utworzeniem ukrytego pola.

Na przykład, jeśli twoja lista jest typu string, możesz połączyć ją w listę oddzieloną przecinkami, a następnie podzielić listę po wysłaniu z powrotem do kontrolera.


4

Właśnie dowiedziałem się (po kilku godzinach prób wyjaśnienia, dlaczego wartości modelu nie wracają do kontrolera), że ukryte dla powinny być zgodne z EditorFor.

O ile nie robię czegoś innego źle, to właśnie znalazłem. Nie popełnię ponownie błędu.

W kontekście modelu zawierającego listę innej klasy.

To NIE zadziała:

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

Gdzie to BĘDZIE ......

            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

Zacząłem przekopywać się w kodzie źródłowym HiddenFori myślę, że przeszkoda, którą widzisz, polega na tym, że twój złożony obiekt MyListnie jest niejawnie konwertowany na typ string, więc struktura traktuje twoją Modelwartość jako nulli renderuje valueatrybut pusty.


3

Możesz rzucić okiem na to rozwiązanie .

Umieść tylko HiddenFor wewnątrz EditorTemplate.

I w swoim Widoku umieść to: @Html.EditorFor(model => model.MyList)

Powinno działać.


3

W obliczu tego samego problemu. Bez pętli for wysłał tylko pierwszy element listy. Po wykonaniu iteracji pętli for może zachować pełną listę i pomyślnie wysyłać.

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

2

Inną opcją byłoby:

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

To był też mój pierwszy pomysł. Ale miałem model widoku, który oczekiwał int [] dla pola MyList, a ciąg oddzielony przecinkami nie jest analizowany do tablicy przez mechanizm wiązania MVC.
Tadej Mali

2

foreachPętli zamiast forpętli może być nieco roztworu czyszczącego.

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

1

Innym możliwym sposobem rozwiązania tego problemu byłoby nadanie każdemu obiektowi z listy identyfikatora, a następnie użycie @Html.DropDownListFor(model => model.IDs)i wypełnienie tablicy, która przechowuje identyfikatory.


1

może późno, ale stworzyłem metodę rozszerzenia dla ukrytych pól z kolekcji (z prostymi elementami typu danych):

Więc oto jest:

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

Użycie jest tak proste, jak:

@Html.HiddenForCollection(m => m.MyList)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.