2011-12-22 18 views
37

¿Es cierto que el archivador de modelo predeterminado en MVC 3.0 es capaz de manejar índices no secuenciales (para los tipos de modelo simples y complejos)? Me he topado con publicaciones que sugieren que sí, pero en mis pruebas parece que NO.Índices no secuenciales de MVC3 y DefaultModelBinder

puesto determinado de nuevo valores:

items[0].Id = 10 
items[0].Name = "Some Item" 
items[1].Id = 3 
items[1].Name = "Some Item" 
items[4].Id = 6 
items[4].Name = "Some Item" 

y un método controlador:

public ActionResult(IList<MyItem> items) { ... } 

Los únicos valores que se cargan son artículos 0 y 1; el elemento 4 simplemente se ignora.

He visto numerosas soluciones para generar índices personalizados (Model Binding to a List), sin embargo, todos parecen apuntar a versiones anteriores de MVC, y la mayoría son un poco 'pesado' IMO.

¿Echo de menos algo?

Respuesta

63

tengo este trabajo, hay que recordar que añadir un campo oculto de indexación común como se explica en el artículo de referencia:

La entrada oculta con name = Items.Index es la parte clave

<input type="hidden" name="Items.Index" value="0" /> 
<input type="text" name="Items[0].Name" value="someValue1" /> 

<input type="hidden" name="Items.Index" value="1" /> 
<input type="text" name="Items[1].Name" value="someValue2" /> 

<input type="hidden" name="Items.Index" value="3" /> 
<input type="text" name="Items[3].Name" value="someValue3" /> 

<input type="hidden" name="Items.Index" value="4" /> 
<input type="text" name="Items[4].Name" value="someValue4" /> 

espero que esto ayude

+0

Tenía la esperanza de evitar Este enfoque. Crucé los dedos para que la carpeta de modelo predeterminada resolviera simplemente el indicio que faltaba. Debe haber una razón (tal vez para situaciones más complejas?) Para tener que especificar explícitamente el índice. De cualquier manera, gracias por la respuesta rápida y el código de muestra. – mindlessgoods

+1

¡Gracias, funciona perfectamente! – Levitikon

+4

¡OH MY NUL! Esto hizo que regresar las listas sea mucho más fácil, en lugar de hacer el ridículo para (i ++), solo puedo usar la clave principal o cualquier otra identificación en el índice, y la lista vuelve muy bien, y fuertemente tipada. Estos oscuros y ocultos secretos. ¡Esto hizo mi dia! +1 + cerveza !!! – ppumkin

4

El artículo al que hizo referencia es uno antiguo (MVC2), pero hasta donde yo sé, esta sigue siendo la manera defacto de modelar las colecciones de enlaces utilizando el enlazador de modelos predeterminado.

Si desea indexación no secuencial, como dice Bassam, deberá especificar un indexador. El indexador no necesita ser numérico.

Utilizamos Steve Sanderson's BeginCollectionItem Html Helper para esto. Genera automáticamente el indexador como un Guid. Creo que este es un mejor enfoque que el uso de indexadores numéricos cuando el elemento HTML de la colección no es secuencial.

+0

Me encontré con el mismo artículo, y definitivamente resolvió el problema que estaba describiendo. Como mencioné anteriormente, esperaba que el encuadernador de modelos predeterminado manejaría esta situación internamente y que el ayudante BeginCollectionItem en realidad no era necesario. ¡Gracias por la respuesta! – mindlessgoods

5

Este método de ayuda, derivado del enfoque de Steve Sanderson, es mucho más simple y puede utilizarse para anclar cualquier elemento de una colección y parece funcionar con el enlace de modelo MVC.

public static IHtmlString AnchorIndex(this HtmlHelper html) 
{ 
    var htmlFieldPrefix = html.ViewData.TemplateInfo.HtmlFieldPrefix; 
    var m = Regex.Match(htmlFieldPrefix, @"([\w]+)\[([\w]*)\]"); 
    if (m.Success && m.Groups.Count == 3) 
     return 
      MvcHtmlString.Create(
       string.Format(
        "<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", 
        m.Groups[1].Value, m.Groups[2].Value)); 
    return null; 
} 

E.g. Simplemente llámelo en un EditorTemplate, o en cualquier otro lugar donde genere entradas, de la siguiente manera para generar el índice de anclaje de la variable oculta, si corresponde.

@model SomeViewModel 
@Html.AnchorIndex() 
@Html.TextBoxFor(m => m.Name) 
... etc. 

Creo que tiene algunas ventajas sobre el enfoque de Steve Sanderson.

  1. Funciona con EditorFor y otros mecanismos incorporados para procesar enumerables. Así que si es un ItemsIEnumerable<T> propiedad en un modelo de vista, los siguientes funciona como se espera:

    <ul id="editorRows" class="list-unstyled"> @Html.EditorFor(m => m.Items) @* Each item will correctly anchor allowing for dynamic add/deletion via Javascript *@ </ul>

  2. Es más sencillo y no requiere ningún tipo de condiciones más mágicos.

  3. Puede tener un solo EditorTemplate/DisplayTemplate para un tipo de datos y simplemente no-op si no se usa en un elemento en una lista.

El único inconveniente es que si el modelo de raíz estando unido es el enumerable (es decir, el parámetro para el propio método de acción y no simplemente una propiedad en algún lugar más profundo en el gráfico parámetro de objeto), la unión se producirá un error en el primer índice no secuencial. Desafortunadamente, la funcionalidad .Index de DefaultModelBinder solo funciona para objetos no raíz. En este escenario, su única opción sigue siendo utilizar los enfoques anteriores.

+0

Muchas gracias por esto Phil. Estoy muy comprometido con el uso de EditorFor con enumerables y esto funcionó como un encanto! – Jono

1

O utilizar esta función javascript para fijar la indexación: (Reemplazar EntityName y NombreCampo obviamente)

function fixIndexing() { 
     var tableRows = $('#tblMyEntities tbody tr'); 

     for (x = 0; x < tableRows.length; x++) { 
      tableRows.eq(x).attr('data-index', x); 

      tableRows.eq(x).children('td:nth-child(1)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName1"); 

      tableRows.eq(x).children('td:nth-child(2)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName2"); 

      tableRows.eq(x).children('td:nth-child(3)').children('input:first').attr('name', 'EntityName[' + x + "].FieldName3"); 
     } 

     return true; //- Submit Form - 
    } 
2

he tenido problemas con esta esta semana y la respuesta de Bassam fue la clave para conseguir conmigo en el camino correcto. Tengo una lista dinámica de artículos de inventario que pueden tener un campo de cantidad. Necesitaba saber cuántos de los elementos seleccionados, excepto la lista de elementos, que pueden variar de 1 a n.

Mi solución fue bastante simple al final. Creé un ViewModel llamado ItemVM con dos propiedades. ItemID y Cantidad. En la acción posterior acepto una lista de estos. Con Indexing activado, todos los elementos se pasan ... incluso con una cantidad nula. Tienes que validar y manejar el lado del servidor, pero con la iteración es trivial manejar esta lista dinámica.

en mi opinión, yo estoy usando algo como esto:

@foreach (Item item in Items) 
{ 
<input type="hidden" name="OrderItems.Index" value="@item.ItemID" /> 
<input type="hidden" name="OrderItems[@item.ItemID].ItemID" value="@item.ItemID" /> 
<input type="number" name="OrderItems[@item.ItemID].Quantity" /> 
} 

Esto me da una lista con un índice basado en 0, pero iteración en el controlador extrae todos los datos necesarios de un nuevo modelo inflexible de tipos .

public ActionResult Marketing(List<ItemVM> OrderItems) 
... 
     foreach (ItemVM itemVM in OrderItems) 
      { 
       OrderItem item = new OrderItem(); 
       item.ItemID = Convert.ToInt16(itemVM.ItemID); 
       item.Quantity = Convert.ToInt16(itemVM.Quantity); 
       if (item.Quantity > 0) 
       { 
        order.Items.Add(item); 
       } 
      } 

A continuación, obtendrá una colección de elementos que tienen una cantidad mayor que 0 y la identificación del artículo.

Esta técnica funciona en MVC 5 utilizando EF 6 en Visual Studio 2015. Quizás esto ayude a alguien a buscar esta solución como yo.

+0

¿Podría proporcionar el ejemplo completo? Tengo una situación muy similar –

1

me terminó haciendo un ayudante HTML más genérico: -

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Text; 
using System.Text.RegularExpressions; 
using System.Web; 
using System.Web.Mvc; 

namespace Wallboards.Web.Helpers 
{ 
    /// <summary> 
    /// Hidden Index Html Helper 
    /// </summary> 
    public static class HiddenIndexHtmlHelper 
    { 
     /// <summary> 
     /// Hiddens the index for. 
     /// </summary> 
     /// <typeparam name="TModel">The type of the model.</typeparam> 
     /// <typeparam name="TProperty">The type of the property.</typeparam> 
     /// <param name="htmlHelper">The HTML helper.</param> 
     /// <param name="expression">The expression.</param> 
     /// <param name="index">The Index</param> 
     /// <returns>Returns Hidden Index For</returns> 
     public static MvcHtmlString HiddenIndexFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int index) 
     { 
      var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); 
      var propName = metadata.PropertyName; 

      StringBuilder sb = new StringBuilder(); 
      sb.AppendFormat("<input type=\"hidden\" name=\"{0}.Index\" autocomplete=\"off\" value=\"{1}\" />", propName, index); 

      return MvcHtmlString.Create(sb.ToString()); 
     } 
    } 
} 

Y luego incluirla en cada iteración del elemento de la lista en la vista de la maquinilla de afeitar: -

@Html.HiddenIndexFor(m => m.ExistingWallboardMessages, i) 
Cuestiones relacionadas