@ Html.HiddenFor non funziona sugli elenchi in ASP.NET MVC


97

Sto usando un modello che contiene un elenco come proprietà. Sto popolando questo elenco con gli elementi che prendo da SQL Server. Desidero che l'elenco venga nascosto nella visualizzazione e passato all'azione POST. In seguito potrei voler aggiungere più elementi a questo elenco con jQuery che rende un array inadatto per l'espansione in seguito. Normalmente useresti

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

per eseguire questa funzionalità, ma per qualche motivo l'elenco in POST è sempre nullo.

Domanda molto semplice, qualcuno sa perché MVC si comporta in questo modo?


1
Normalmente non nasconderesti interi elenchi in questo modo. Qual è il tuo output desiderato in termini di <input />s?
Cᴏʀʏ

1
cosa MyListcontiene? HiddenForviene utilizzato solo per un ingresso alla volta.
Daniel A. White

1
Che tipo è Model.MyList? Potrebbe essere necessario eseguire manualmente alcune serializzazioni / deserializzazioni nell'elenco.
Kyle Trauberman


Risposte:


161

Ho appena riscontrato questo problema e l'ho risolto semplicemente procedendo come segue:

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

Utilizzando un for invece di un foreach l'associazione del modello funzionerà correttamente e raccoglierà tutti i valori nascosti nell'elenco. Sembra il modo più semplice per risolvere questo problema.


5
Grazie! salvato la mia notte.
TSmith

7
Grazie - bella soluzione semplice. Tuttavia, è necessaria solo una piccola mod: è necessario fare riferimento al campo Id dell'oggetto. Quindi, se il campo si chiama RowId, allora:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta

3
ha funzionato per me, anche quando avevo più campi sui modelli della collezione. Cioè @Html.EditorFor(model => Model.ToGroups[i].Id)seguito da @Html.EditorFor(model => Model.ToGroups[i].Description)la prossima volta - entrambi nel ciclo for. E il controller è stato in grado di mapparlo a un elenco dei modelli con quei campi. E per assicurarti che niente di tutto ciò appaia sullo schermo, circondalo semplicemente<div style="display: none;"></div>
Don Cheadle

Brillante! Ben fatto. Ha funzionato per me!
AxleWack

3
@ user3186023 Rispondere a un commento molto vecchio qui, ma forse qualcun altro avrà lo stesso problema: Cambia il forloop in questo:for(int i = 0; i < Model.Departments.Count(); i++)
Stian

28

HiddenFor non è come DisplayFor o EditorFor. Non funzionerà con le raccolte, solo valori singoli.

Puoi utilizzare l'helper Serialize HTML disponibile nel progetto MVC Futures per serializzare un oggetto in un campo Hidden, oppure dovrai scrivere il codice da solo. Una soluzione migliore consiste nel serializzare semplicemente un ID di qualche tipo e riottenere i dati dal database durante il postback.


Hai un esempio? Ho provato questo e non è riuscito a collegarsi al valore ViewModel quando è stato inviato il modulo.
Alan Macdonald

@AlanMacdonald - se qualcosa non riesce a legarsi, è perché la tua denominazione non è corretta, più che probabile perché hai usato un foreach invece di un for with indexer. O forse non hai utilizzato gli attributi corretti nell'associazione. Vedi weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik

Grazie. In realtà quando ho provato è stato letteralmente @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs) dove Model era il mio ViewModel e aveva una proprietà ModelIDs int array. Quindi non c'erano loop o altro. Quando il modulo è stato inviato, i ModelID erano sempre nulli nel ViewModel associato.
Alan Macdonald

@AlanMacdonald - Non includi "Model" nel nome.
Erik Funkenbusch

16

È un po 'un trucco, ma se @Html.EditorForo @Html.DisplayForfunziona per il tuo elenco, se vuoi assicurarti che sia inviato sulla richiesta del post ma non visibile, potresti semplicemente modellarlo in modo display: none;da nasconderlo, ad esempio:

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

Ciò non salva il valore nel modello al momento della pubblicazione della richiesta.
nldev

Se .EditorFor è impostato per funzionare correttamente, anche questo dovrebbe funzionare, credo.
Mark Rhodes

9

Che ne dici di usare Newtonsoft per deserializzare l'oggetto in una stringa json e quindi inserirlo nel tuo campo Hidden, ad esempio ( Model.DataResponse.Entity.Commission è un elenco di semplici oggetti "CommissionRange" come vedrai nel 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]
   }

Rende come:

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

Nel mio caso faccio alcune cose JS per modificare il json nel campo nascosto prima di postare di nuovo

Nel mio controller utilizzo nuovamente Newtonsoft per deserializzare:

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

Questo ha funzionato per me. Ho pensato che fosse molto più pulito che la soluzione accettata.
e-il

6

Html.HiddenForè progettato per un solo valore. Dovrai serializzare il tuo elenco in qualche modo prima di creare il campo nascosto.

Ad esempio, se il tuo elenco è di tipo stringa, puoi unirti all'elenco in un elenco separato da virgole, quindi dividere l'elenco dopo il post nel controller.


4

Ho appena scoperto (dopo un paio d'ore di tentativi di capire perché i valori del modello non tornavano al controller) che nascosto per dovrebbe seguire l'EditorFor.

A meno che non stia facendo qualcos'altro di sbagliato, questo è ciò che ho trovato. Non commetterò più l'errore.

Nel contesto di un modello che contiene un elenco di un'altra classe.

Questo NON funzionerà:

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

Dove come questo ...

            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

Ho iniziato a scavare nel codice sorgente HiddenFore penso che il blocco che stai vedendo è che il tuo oggetto complesso MyListnon è implicitamente convertibile in tipo string, quindi il framework considera il tuo Modelvalore come nulle rende l' valueattributo vuoto.


3

Puoi dare un'occhiata a questa soluzione .

Metti solo HiddenFor all'interno dell'EditorTemplate.

E nella tua vista metti questo: @Html.EditorFor(model => model.MyList)

Dovrebbe funzionare.


3

Ha affrontato lo stesso problema. Senza ciclo for, ha pubblicato solo il primo elemento della lista. Dopo l'iterazione del ciclo for, può mantenere l'elenco completo e pubblicare correttamente.

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

2

Un'altra opzione potrebbe essere:

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

Questa è stata anche la mia prima idea. Ma avevo un modello di visualizzazione, che prevedeva un int [] per il campo MyList e la stringa separata da virgole non viene analizzata in un array dal meccanismo di associazione MVC.
Tadej Mali

2

Il foreachloop invece di un forloop potrebbe essere una soluzione leggermente più pulita.

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

1

Un altro modo possibile per risolvere questo problema sarebbe dare a ogni oggetto nel tuo elenco un ID, quindi utilizzare @Html.DropDownListFor(model => model.IDs)e popolare un array che contiene gli ID.


1

forse in ritardo, ma ho creato un metodo di estensione per i campi nascosti dalla raccolta (con semplici elementi di tipo di dati):

Quindi eccolo qui:

/// <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'utilizzo è semplice come:

@Html.HiddenForCollection(m => m.MyList)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.