@ Html.HiddenFor no funciona en listas en ASP.NET MVC

97

Estoy usando un modelo que contiene una lista como propiedad. Estoy completando esta lista con elementos que obtengo de SQL Server. Quiero que la Lista se oculte en la vista y se pase a la acción POST. Más adelante, es posible que desee agregar más elementos a esta lista con jQuery, lo que hace que una matriz no sea adecuada para la expansión más adelante. Normalmente usarías

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

para lograr esta funcionalidad, pero por alguna razón la Lista en POST siempre es nula.

Pregunta muy simple, ¿alguien sabe por qué MVC se comporta así?

Anton Smith
fuente
1
Normalmente, no escondería listas enteras como esa. ¿Cuál es su salida deseada en términos de <input />s?
Cᴏʀʏ
1
que MyListcontiene HiddenForsolo se usa para una entrada a la vez.
Daniel A. White
1
¿Qué tipo es Model.MyList? Es posible que deba realizar alguna serialización / deserialización en su lista manualmente.
Kyle Trauberman
1
[ stackoverflow.com/questions/4381871/… Pregunta similar.
Sanjeevi Subramani
1
Pregunta similar: Uso de HiddenFor con intellisense
Sanjeevi Subramani

Respuestas:

161

Me encontré con este problema y lo resolví simplemente haciendo lo siguiente:

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

Al usar un for en lugar de un foreach, el enlace del modelo funcionará correctamente y recogerá todos sus valores ocultos en la lista. Parece la forma más sencilla de resolver este problema.

Daniel Mackay
fuente
5
¡Gracias! salvó mi noche.
TSmith
7
Gracias - bonita y sencilla solución. Sin embargo, solo se necesita una pequeña modificación: se debe hacer referencia al campo Id del objeto. Entonces, si el campo se llama RowId, entonces:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta
3
funcionó para mí, incluso cuando tenía varios campos en los modelos de la colección. Es decir, @Html.EditorFor(model => Model.ToGroups[i].Id)seguido de @Html.EditorFor(model => Model.ToGroups[i].Description)la próxima vez, ambos en el bucle for. Y el controlador pudo asignarlo a una lista de los modelos con esos campos. Y para asegurarse de que nada de eso aparezca en la pantalla, simplemente envuélvalo en<div style="display: none;"></div>
Don Cheadle
¡Brillante! Bien hecho. ¡Trabajó para mi!
AxleWack
3
@ user3186023 Respondiendo a un comentario muy antiguo aquí, pero tal vez alguien más tenga el mismo problema: Cambie el forbucle a esto:for(int i = 0; i < Model.Departments.Count(); i++)
Stian
28

HiddenFor no es como DisplayFor o EditorFor. No funcionará con colecciones, solo valores individuales.

Puede usar el ayudante Serialize HTML disponible en el proyecto MVC Futures para serializar un objeto en un campo oculto, o tendrá que escribir el código usted mismo. Una mejor solución es simplemente serializar un ID de algún tipo y volver a obtener los datos de la base de datos en la devolución.

Erik Funkenbusch
fuente
¿Tienes un ejemplo? Intenté esto y no se pudo vincular al valor de ViewModel cuando se envió el formulario.
Alan Macdonald
@AlanMacdonald: si algo no se enlaza, es porque su nombre no es correcto, más que probablemente porque usó un foreach en lugar de un for con indexador. O tal vez no usó los atributos adecuados en el enlace. Ver weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch
Gracias. En realidad, cuando lo intenté fue literalmente @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs) donde Model era mi ViewModel y tenía una propiedad de matriz int ModelIDs. Entonces no hubo bucles ni nada. Cuando se envió el formulario, los ModelID siempre eran nulos en el ViewModel vinculado.
Alan Macdonald
@AlanMacdonald: no incluye "Modelo" en el nombre.
Erik Funkenbusch
16

Es un truco, pero si @Html.EditorForo @Html.DisplayForfunciona para su lista, si desea asegurarse de que se envíe en la solicitud de publicación pero no sea visible, puede diseñarlo para usarlo display: none;para ocultarlo, por ejemplo:

<div style="display: none;">@Html.EditorFor(model => model.MyList)</div>
Mark Rhodes
fuente
Esto no guarda el valor en el modelo al publicar la solicitud.
nldev
Si .EditorFor está configurado para funcionar correctamente, creo que esto también debería funcionar.
Mark Rhodes
9

¿Qué hay de usar Newtonsoft para deserializar el objeto en una cadena json y luego insertarlo en su campo oculto, por ejemplo ( Model.DataResponse.Entity.Commission es una lista de objetos simples "CommissionRange" como verá en el 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]
   }

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

En mi caso, hago algunas cosas de JS para editar el json en el campo oculto antes de volver a publicar

En mi controlador, luego uso Newtonsoft nuevamente para deserializar:

string jsonCommissionRange = Request.Form["DataResponse_Entity_Commission"];
List<CommissionRange> commissionRange = JsonConvert.DeserializeObject<List<CommissionRange>>(jsonCommissionRange);
Adam Hey
fuente
Esto funcionó para mí. Pensé que era mucho más limpio que la solución aceptada.
e-el
6

Html.HiddenForestá diseñado para un solo valor. Deberá serializar su lista de alguna manera antes de crear el campo oculto.

Por ejemplo, si su lista es de tipo cadena, puede unir la lista en una lista separada por comas, luego dividir la lista después de la publicación en su controlador.

Kyle Trauberman
fuente
4

Me acabo de enterar (después de un par de horas de tratar de averiguar por qué los valores del modelo no regresaban al controlador) que los valores ocultos deben seguir al EditorFor.

A menos que esté haciendo algo más mal, esto es lo que encontré. No volveré a cometer el error.

En el contexto de un modelo que contiene una lista de otra clase.

Esto no 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>
            }
        }

Donde como esto VOLVERÁ ...

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

Comencé a buscar en el código fuente HiddenFory creo que el obstáculo que está viendo es que su objeto complejo MyListno es implícitamente convertible a tipo string, por lo que el marco trata su Modelvalor como nully deja el valueatributo vacío.

Cᴏʀʏ
fuente
3

Puedes echar un vistazo a esta solución .

Coloque solo HiddenFor dentro de EditorTemplate.

Y en tu Vista pon esto: @Html.EditorFor(model => model.MyList)

Debería funcionar.

Wilfart Benjamin
fuente
3

Enfrentó el mismo problema. Sin el bucle for, solo publicó el primer elemento de la lista. Después de recorrer el bucle for, puede mantener la lista completa y publicar con éxito.

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

Otra opcion seria:

<input type="hidden" value=@(string.Join(",", Model.MyList)) />
TiagoBrenck
fuente
Esta también fue mi primera idea. Pero tenía un modelo de vista, que esperaba un int [] para el campo MyList y la cadena separada por comas no se analiza en una matriz mediante el mecanismo de enlace MVC.
Tadej Mali
2

El foreachbucle en lugar de un forbucle podría ser una solución un poco más limpia.

@foreach(var item in Model.ToGroups)
{
    @Html.HiddenFor(model => item)
}
Sebastián
fuente
1

Otra forma posible de solucionar esto sería darle a cada objeto en su Lista una ID, luego usar @Html.DropDownListFor(model => model.IDs)y completar una matriz que contenga las ID.

deckeresq
fuente
1

tal vez tarde, pero creé un método de extensión para campos ocultos de la colección (con elementos de tipo de datos simples):

Asi que aqui esta:

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

El uso es tan simple como:

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