@ Html.HiddenFor não funciona em listas na ASP.NET MVC

97

Estou usando um modelo que contém uma lista como propriedade. Estou preenchendo esta lista com itens que peguei do SQL Server. Quero que a lista seja ocultada na visualização e passada para a ação POST. Posteriormente, posso querer adicionar mais itens a esta Lista com jQuery, o que torna um array inadequado para expansão posterior. Normalmente você usaria

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

para realizar essa funcionalidade, mas por algum motivo a Lista no POST é sempre nula.

Pergunta muito simples, alguém sabe por que MVC se comporta assim?

Anton Smith
fonte
1
Normalmente você não esconderia listas inteiras assim. Qual é a saída desejada em termos de <input />s?
Cᴏʀʏ
1
o que MyListcontém? HiddenForé usado apenas para uma entrada de cada vez.
Daniel A. White
1
Qual é o tipo Model.MyList? Pode ser necessário executar alguma serialização / desserialização em sua lista manualmente.
Kyle Trauberman
1
[ stackoverflow.com/questions/4381871/… Pergunta semelhante.
Sanjeevi Subramani
1
Pergunta semelhante: Uso de HiddenFor com intellisense
Sanjeevi Subramani

Respostas:

161

Acabei de encontrar esse problema e resolvi-o simplesmente fazendo o seguinte:

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

Usando um for em vez de um foreach, a vinculação do modelo funcionará corretamente e coletará todos os seus valores ocultos na lista. Parece a maneira mais simples de resolver esse problema.

Daniel Mackay
fonte
5
Obrigado! salvou minha noite.
TSmith
7
Obrigado - boa solução simples. Porém, apenas um pequeno mod é necessário: o campo Id do objeto precisa ser referido. Portanto, se o campo for chamado RowId, então:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta
3
funcionou para mim, mesmo quando eu tinha vários campos nos modelos da coleção. Ou seja, @Html.EditorFor(model => Model.ToGroups[i].Id)seguido por @Html.EditorFor(model => Model.ToGroups[i].Description)na próxima vez - ambos no loop for. E o controlador conseguiu mapeá-lo para uma lista de modelos com esses campos. E para garantir que nada disso apareça na tela, basta envolvê-lo em<div style="display: none;"></div>
Don Cheadle
Brilhante! Bem feito. Funcionou para mim!
AxleWack de
3
@ user3186023 Respondendo a um comentário muito antigo aqui, mas talvez outra pessoa tenha o mesmo problema: Mude o for-loop para este:for(int i = 0; i < Model.Departments.Count(); i++)
Stian
28

HiddenFor não é como DisplayFor ou EditorFor. Não funcionará com coleções, apenas valores únicos.

Você pode usar o auxiliar Serialize HTML disponível no projeto MVC Futures para serializar um objeto para um campo Oculto ou terá que escrever o código você mesmo. Uma solução melhor é simplesmente serializar um ID de algum tipo e recuperar os dados do banco de dados no postback.

Erik Funkenbusch
fonte
Você tem um exemplo? Tentei fazer isso e não conseguiu vincular ao valor ViewModel quando o formulário foi enviado.
Alan Macdonald,
@AlanMacdonald - se algo falha na ligação, é porque sua nomenclatura não está correta, mais do que provavelmente porque você usou um foreach em vez de um for com indexador. Ou talvez você não tenha usado os atributos adequados na ligação. Consulte weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch
Obrigado. Na verdade, quando tentei, foi literalmente @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs) onde Model era meu ViewModel e tinha uma propriedade de array int ModelIDs. Portanto, não houve loops nem nada. Quando o formulário foi enviado, os ModelIDs eram sempre nulos no ViewModel vinculado.
Alan Macdonald,
@AlanMacdonald - Você não inclui "Modelo" no nome.
Erik Funkenbusch
16

É um pouco de um truque, mas se @Html.EditorForou @Html.DisplayFortrabalho para a sua lista, se você quer ter certeza que é enviado na solicitação post, mas não visível, você pode simplesmente estilo para usar display: none;para escondê-lo em vez disso, por exemplo:

<div style="display: none;">@Html.EditorFor(model => model.MyList)</div>
Mark Rhodes
fonte
Isso não salva o valor no modelo após a solicitação.
nldev
Se .EditorFor estiver configurado para funcionar corretamente, então isso também deve funcionar, eu acredito.
Mark Rhodes
9

Que tal usar o Newtonsoft para desserializar o objeto em uma string json e, em seguida, inserir isso em seu campo Hidden, por exemplo ( Model.DataResponse.Entity.Commission é uma lista de objetos "CommissionRange" simples como você verá no 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]
   }

Renderiza como:

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

No meu caso, faço algumas coisas em JS para editar o json no campo oculto antes de postar de volta

Em meu controlador, uso o Newtonsoft novamente para desserializar:

string jsonCommissionRange = Request.Form["DataResponse_Entity_Commission"];
List<CommissionRange> commissionRange = JsonConvert.DeserializeObject<List<CommissionRange>>(jsonCommissionRange);
Adam Ei
fonte
Isso funcionou para mim. Achei muito mais limpo que a solução aceita.
e-em
6

Html.HiddenForé projetado para apenas um valor. Você precisará serializar sua lista de alguma forma antes de criar o campo oculto.

Por exemplo, se sua lista for do tipo string, você pode juntar a lista em uma lista separada por vírgulas e, em seguida, dividir a lista após postar de volta em seu controlador.

Kyle Trauberman
fonte
4

Acabei de descobrir (depois de algumas horas tentando descobrir por que os valores do modelo não estavam voltando para o controlador) que oculto para deve seguir o EditorFor.

A menos que eu esteja fazendo algo errado, foi isso que descobri. Não cometerei o erro de novo.

No contexto de um modelo que contém uma lista de outra classe.

Isso NÃO vai funcionar:

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

Onde assim VAI ......

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

Comecei a vasculhar o código-fonte de HiddenFor, e acho que o obstáculo que você está vendo é que seu objeto complexo MyListnão é implicitamente conversível em tipo string, portanto, a estrutura trata seu Modelvalor como nulle torna o valueatributo vazio.

Cᴏʀʏ
fonte
3

Você pode dar uma olhada nesta solução .

Coloque apenas HiddenFor dentro do EditorTemplate.

E em sua visão coloque isto: @Html.EditorFor(model => model.MyList)

Deve funcionar.

Wilfart Benjamin
fonte
3

Enfrentou o mesmo problema. Sem o loop for, ele postou apenas o primeiro elemento da lista. Depois de iterar pelo loop for, ele pode manter a lista completa e postar com sucesso.

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

Outra opção seria:

<input type="hidden" value=@(string.Join(",", Model.MyList)) />
TiagoBrenck
fonte
Essa também foi minha primeira ideia. Mas eu tinha um modelo de exibição que esperava um int [] para o campo MyList e a string separada por vírgulas não é analisada em uma matriz pelo mecanismo de vinculação MVC.
Tadej Mali
2

O foreachloop em vez de um forloop pode ser uma solução ligeiramente mais limpa.

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

Outra maneira possível de corrigir isso seria dar a cada objeto em sua lista um ID e, em seguida, usar @Html.DropDownListFor(model => model.IDs)e preencher uma matriz que contém os IDs.

deckeresq
fonte
1

talvez tarde, mas criei um método de extensão para campos ocultos da coleção (com itens de tipo de dados simples):

Então aqui está:

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

O uso é tão simples quanto:

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