Непоследовательные индексы MVC3 и DefaultModelBinder - PullRequest
40 голосов
/ 22 декабря 2011

Правда ли, что механизм связывания моделей по умолчанию в 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, и большинство из них немного «жесткие» IMO.

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

Ответы [ 6 ]

67 голосов
/ 22 декабря 2011

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

Скрытый ввод с 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" />

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

5 голосов
/ 24 января 2014

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

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

  1. Работает с 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>

  2. Это проще и не требует больше волшебных строк.

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

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

4 голосов
/ 22 декабря 2011

Статья, на которую вы ссылались, старая (MVC2), но, насколько я знаю, это все еще неэффективный способ моделирования коллекций связываний с использованием связывателя моделей по умолчанию.

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

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

2 голосов
/ 10 октября 2015

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

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

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

@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. Возможно, это поможет кому-то искать это решение, как я.

1 голос
/ 10 марта 2017

В итоге я создал более общий HTML Helper: -

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)
1 голос
/ 14 января 2015

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

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