MVC3 Non-Sequential Indices and DefaultModelBinder

est-il vrai que le liant de modèle par défaut dans MVC 3.0 est capable de gérer des indices non séquentiels (pour les types de modèles simples et complexes)? J'ai rencontré des messages qui suggèrent qu'il devrait, cependant dans mes tests, il semble QU'il ne le fait pas.

compte tenu de renvoyer les valeurs:

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"

Et une méthode de contrôleur:

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

les seules valeurs qui sont chargées sont les items 0 et 1; l'item 4 est tout simplement ignoré.

j'ai vu de nombreuses solutions pour générer des indices personnalisés (liaison du modèle à une liste), cependant, ils semblent tous cibler des versions précédentes de MVC, et la plupart sont un peu "lourds" IMO.

est-ce que j'ai raté quelque chose?

40
demandé sur Leniel Maccaferri 2011-12-22 04:49:11

6 réponses

j'ai ceci qui fonctionne, vous devez vous rappeler d'ajouter une entrée cachée d'indexation commune comme expliqué dans votre article référencé:

l'entrée cachée avec name = Items.Index est la clé de la partie

<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" />

j'espère que cela aidera

67
répondu Bassam Mehanni 2014-05-29 17:57:27

cette méthode d'aide, dérivée de L'approche de Steve Sanderson, est beaucoup plus simple et peut être utilisée pour ancrer n'importe quel article dans une collection et elle semble fonctionner avec MVC modèle binding.

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. Il suffit de l'appeler dans un fichier Editoremplate, ou n'importe où ailleurs vous généreriez des entrées, comme suit pour générer la variable d'ancrage d'index cachée si une est applicable.

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

je pense qu'il a quelques avantages par rapport à celui de Steve Sanderson approche.

  1. il fonctionne avec EditorFor et d'autres mécanismes intégrés pour le traitement des enumerables. Donc, si Items est un IEnumerable<T> propriété sur un modèle de vue, les ouvrages suivants, comme prévu:

    <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. C'est plus simple et ne nécessite pas plus de chaînes magiques.

  3. vous pouvez avoir un Éditortemplate/DisplayTemplate unique pour un type de données et il sera tout simplement no-op s'il n'est pas utilisé sur un élément dans un liste.

le seul inconvénient est que si le modèle racine étant lié est l'énumérable (c.-à-d. le paramètre à la méthode D'Action elle-même et pas simplement une propriété quelque part plus profonde dans le graphique d'objet paramètre), la liaison échouera au premier index non séquentiel. Malheureusement, le .Index la fonctionnalité du relieur Defaultmodel ne fonctionne que pour les objets non-root. Dans ce scénario, votre seule option reste d'utiliser les approches ci-dessus.

5
répondu Phil Degenhardt 2014-01-24 04:11:02

l'article auquel vous avez fait référence est un ancien (MVC2), mais pour autant que je sache, c'est toujours la manière de facto de modéliser les collections bind en utilisant le modelbinder par défaut.

si vous voulez une indexation non séquentielle, comme dit Bassam, vous devrez spécifier un indexeur. Il n'est pas nécessaire que l'indexeur soit numérique.

Nous utilisons Steve Sanderson's BeginCollectionItem Html Helper pour cela. Il génère automatiquement l'indexeur en tant que guide. Je pense que c'est mieux approche plutôt que d'utiliser des indexeurs numériques lorsque votre article de collection HTML n'est pas séquentiel.

4
répondu danludwig 2011-12-22 03:57:01

j'ai eu du mal avec ça cette semaine et la réponse de Bassam était la clé pour me mettre sur la bonne voie. J'ai une liste dynamique de l'inventaire des éléments qui peuvent avoir un champ quantité. J'avais besoin de savoir combien d'articles ils avaient choisis, sauf que la liste des articles peut varier de 1 à n.

ma solution était assez simple à la fin. J'ai créé un ViewModel appelé ItemVM avec deux propriétés. ItemID and Quantity. Dans l'action post j'accepte une liste de ceux-ci. Avec Indexé, tous les items sont passés.. même avec une quantité nulle. Vous devez valider et gérer du côté du serveur informatique, mais avec l'itération, il est trivial de gérer cette liste dynamique.

À mon avis, je suis en utilisant quelque chose comme ceci:

@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" />
}

cela me donne une liste avec un Index basé sur 0, mais l'itération dans le contrôleur extrait toutes les données nécessaires à partir d'un nouveau modèle fortement typé.

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

vous finirez alors avec une collection d'articles qui ont une quantité supérieur à 0, et L'élément ID.

cette technique fonctionne dans MVC 5 en utilisant EF 6 Dans Visual Studio 2015. Peut-être que ça aidera quelqu'un qui cherche Cette solution comme je l'étais.

2
répondu Jason Conville 2015-10-10 15:04:16

Ou utiliser cette fonction javascript pour corriger l'indexation: (Remplacer EntityName et FieldName évidemment)

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 -
    }
1
répondu SlimSjakie 2015-01-14 08:55:57

j'ai fini par créer un Helper HTML plus générique: -

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

Et puis l'inclure dans chaque itération de l'élément de la liste de votre Rasoir:

@Html.HiddenIndexFor(m => m.ExistingWallboardMessages, i)
1
répondu Stephen Garside 2017-03-10 09:55:26