MVC3 Non-Sequential Indices и DefaultModelBinder

Верно ли, что связующее устройство по умолчанию в MVC 3.0 способно обрабатывать несекретные индексы (как для простых, так и для сложных типов моделей)? Я столкнулся с сообщениями, которые предполагают, что это должно произойти, однако в моих тестах, похоже, что это НЕ.

Указанные значения обратной связи:

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"

И метод контроллера:

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

Загружаются только значения 0 и 1; элемент 4 просто игнорируется.

Я видел множество решений для создания пользовательских индексов (привязка к списку в списке), однако все они, похоже, нацелены на предыдущие версии MVC, и большинство из них являются бит "тяжелой" ИМО.

Я что-то пропустил?

Ответ 1

У меня есть эта работа, вы должны помнить о том, чтобы добавить общий индексный скрытый ввод, как описано в вашей статье:

Скрытый ввод с name = Items.Index является ключевой частью

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

надеюсь, что это поможет

Ответ 2

Этот вспомогательный метод, основанный на подходе Стива Сандерсона, намного проще и может быть использован для привязки любого элемента в коллекции и, похоже, работает с привязкой к модели 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;
}

например. Просто назовите его в EditorTemplate или где-нибудь еще, вы будете генерировать входы, как показано ниже, чтобы создать привязку привязки индекса, если это применимо.

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

Я думаю, что у него есть несколько преимуществ по сравнению с подходом Стива Сандерсона.

  • Он работает с EditorFor и другими встроенными механизмами обработки перечислимых. Поэтому, если Items является свойством IEnumerable<T> на модели представления, выполняется следующее:

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

  • Это проще и не требует больше магических строк.

  • У вас может быть один шаблон EditorTemplate/DisplayTemplate для типа данных, и он просто не работает, если не используется в элементе в списке.

Единственный недостаток заключается в том, что если связанная корневая модель является перечислимой (т.е. параметром самого метода Action, а не просто чем-то более глубоким в графе объектов параметров), привязка будет неудачной при первом неселективном индекс. К сожалению, функциональность .Index для DefaultModelBinder работает только для объектов без полномочий root. В этом случае вам остается только использовать вышеприведенные подходы.

Ответ 3

Статья, на которую вы ссылаетесь, является старой (MVC2), но, насколько я знаю, это по-прежнему метод defacto для моделирования коллекций привязок с использованием стандартного модуля моделирования.

Если вам нужна не последовательная индексация, как говорит Bassam, вам нужно будет указать индексатор. Индексатор не должен быть числовым.

Мы используем Steve Sanderson BeginCollectionItem Html Helper для этого. Он автоматически генерирует указатель как Guid. Я думаю, что это лучший подход, чем использование числовых индексаторов, когда ваш элемент коллекции HTML не является последовательным.

Ответ 4

Я боролся с этим на этой неделе, и ответ Бассама был ключом к тому, чтобы меня на правильном пути. У меня есть динамический список элементов инвентаря, который может иметь поле количества. Мне нужно было знать, сколько из них элементов, которые они выбрали, кроме списка элементов, может варьироваться от 1 до n.

Мое решение было довольно простым в конце. Я создал ViewModel под названием ItemVM с двумя свойствами. ItemID и количество. В post post я принимаю список из них. При индексировании все элементы передаются... даже с нулевым количеством. Вы должны проверить и обработать его на стороне сервера, но с итерацией тривиально обращаться с этим динамическим списком.

В моем представлении я использую что-то вроде этого:

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

Это дает мне список с индексом 0, но итерация в контроллере извлекает все необходимые данные из новой сильно типизированной модели.

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

Затем вы получите набор элементов, количество которых больше 0, и идентификатор элемента.

Этот метод работает в MVC 5 с использованием EF 6 в Visual Studio 2015. Возможно, это поможет кому-то найти такое решение, как я.

Ответ 5

Или используйте эту функцию javascript, чтобы исправить индексирование: (Замените имя EntityName и полезна),

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

Ответ 6

В итоге я создал более общий HTML-помощник: -

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

И затем включите его в каждую итерацию элемента списка в вашем представлении Razor: -

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