MVC3 Indici non sequenziali e DefaultModelBinder

È vero che il modello predefinito in MVC 3.0 è in grado di gestire indici non sequenziali (per tipi di modelli sia semplici che complessi)? Mi sono imbattuto in post che suggeriscono che dovrebbe, tuttavia nei miei test sembra che NON lo sia.

Dati i valori postback:

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" 

E un metodo di controllo:

 public ActionResult(IList items) { ... } 

Gli unici valori caricati sono gli articoli 0 e 1; l’articolo 4 è semplicemente ignorato.

Ho visto numerose soluzioni per generare indici personalizzati ( Model Binding a un elenco ), tuttavia sembrano tutti destinati a versioni precedenti di MVC e la maggior parte di essi sono un IMO un po ‘”pesante”.

Mi sto perdendo qualcosa?

Ho questo lavoro, devi ricordare di aggiungere un input nascosto di indicizzazione comune come spiegato nel tuo articolo di riferimento:

L’input nascosto con name = Items.Index è la parte fondamentale

         

spero che questo ti aiuti

Questo metodo di supporto, derivato dall’approccio di Steve Sanderson, è molto più semplice e può essere utilizzato per ancorare qualsiasi elemento in una raccolta e sembra funzionare con il binding del modello 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( "", m.Groups[1].Value, m.Groups[2].Value)); return null; } 

Ad esempio, basta chiamarlo in un EditorTemplate o in qualsiasi altro punto in cui si generano input, come indicato di seguito per generare la variabile nascosta di ancoraggio dell’indice, se applicabile.

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

Penso che abbia alcuni vantaggi rispetto all’approccio di Steve Sanderson.

  1. Funziona con EditorFor e altri meccanismi integrati per l’elaborazione di oggetti enumerabili. Quindi se Items è una proprietà IEnumerable su un modello di vista, il seguente funziona come previsto:

      @Html.EditorFor(m => m.Items) @* Each item will correctly anchor allowing for dynamic add/deletion via Javascript *@

  2. È più semplice e non richiede più stringhe magiche.

  3. È ansible avere un solo EditorTemplate / DisplayTemplate per un tipo di dati e sarà semplicemente non operativo se non utilizzato su un elemento in un elenco.

L’unico svantaggio è che se il modello di root associato è l’enumerabile (cioè il parametro del metodo Action stesso e non semplicemente una proprietà da qualche parte più in profondità nel grafico dell’object parametro), il binding fallirà al primo indice non sequenziale. Sfortunatamente, la funzionalità .Index di DefaultModelBinder funziona solo per oggetti non root. In questo scenario, la tua unica opzione rimane usare gli approcci di cui sopra.

L’articolo a cui si fa riferimento è vecchio (MVC2), ma per quanto ne so, questo è ancora il modo de facto per modellare le collezioni di bind usando il modelbinder predefinito.

Se si desidera l’indicizzazione non sequenziale, come dice Bassam, è necessario specificare un indicizzatore. L’indicizzatore non ha bisogno di essere numerico.

Per questo usiamo l’helper Html BeginCollectionItem di Steve Sanderson . Genera automaticamente l’indicizzatore come un Guid. Penso che questo sia un approccio migliore rispetto all’utilizzo di indicizzatori numerici quando l’elemento HTML della raccolta non è sequenziale.

Sono stato alle prese con questa settimana e la risposta di Bassam è stata la chiave per portarmi sulla strada giusta. Ho un elenco dinamico di articoli di inventario che possono avere un campo quantità. Avevo bisogno di sapere quanti di questi elementi hanno selezionato, ad eccezione dell’elenco di elementi che possono variare da 1 a n .

Alla fine la mia soluzione era piuttosto semplice. Ho creato un ViewModel chiamato ItemVM con due proprietà. ItemID e quantità. Nella post azione accetto un elenco di questi. Con l’indicizzazione triggersta, tutti gli elementi vengono passati .. anche con una quantità nulla. Devi convalidarlo e gestirlo lato server, ma con l’iterazione è banale gestire questo elenco dinamico.

Nella mia vista sto usando qualcosa del genere:

 @foreach (Item item in Items) {    } 

Questo mi dà una lista con un indice a 0, ma l’iterazione nel controller estrae tutti i dati necessari da un nuovo modello fortemente tipizzato.

 public ActionResult Marketing(List 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); } } 

Si finirà quindi con un insieme di articoli che hanno una quantità maggiore di 0 e l’ID object.

Questa tecnica funziona in MVC 5 utilizzando EF 6 in Visual Studio 2015. Forse questo aiuterà qualcuno che sta cercando questa soluzione come lo ero io.

Oppure usa questa funzione javascript per correggere l’indicizzazione: (Sostituisci EntityName e FieldName ovviamente)

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

Ho finito per creare un helper HTML più generico: –

 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 { ///  /// Hidden Index Html Helper ///  public static class HiddenIndexHtmlHelper { ///  /// Hiddens the index for. ///  /// The type of the model. /// The type of the property. /// The HTML helper. /// The expression. /// The Index /// Returns Hidden Index For public static MvcHtmlString HiddenIndexFor(this HtmlHelper htmlHelper, Expression> expression, int index) { var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); var propName = metadata.PropertyName; StringBuilder sb = new StringBuilder(); sb.AppendFormat("", propName, index); return MvcHtmlString.Create(sb.ToString()); } } } 

E poi includilo in ogni iterazione dell’elemento di lista nella tua visualizzazione Razor: –

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