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

Anton Smith
źródło
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
[ stackoverflow.com/questions/4381871/ ... Podobne pytanie.
Sanjeevi Subramani,
1
Podobne pytanie: Używanie HiddenFor z
inteligencją

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.

Daniel Mackay
źródło
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.

Erik Funkenbusch
źródło
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>
Mark Rhodes
źródło
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);
Adam Hej
źródło
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.

Kyle Trauberman
źródło
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>
            }
AntDC
źródło
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.

Cᴏʀʏ
źródło
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ć.

Wilfart Benjamin
źródło
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])
      }
    }
Keerthi
źródło
2

Inną opcją byłoby:

<input type="hidden" value=@(string.Join(",", Model.MyList)) />
TiagoBrenck
źródło
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)
}
Sebastian
źródło
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.

deckeresq
źródło
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)
Gh61
źródło