Ответ 1
Вам не нужно явно индексировать плоские данные. Если у вас есть
<input type='text' name='data' value='George' />
<input type='text' name='data' value='John' />
<input type='text' name='data' value='Paul' />
<input type='text' name='data' value='Ringo' />
Затем в вашем контроллере вы можете использовать
public ActionResult Create(string[] data)
{
//data should now be a string array of 4 elements
return RedirectToAction("Index");
}
Чтобы понять связующее, в основном работайте в обратном направлении. Когда вы отправляете свою форму, при условии, что она отправляется на ваш метод Create, связующее устройство моделирует параметры метода. Он увидит, что у вас есть массив строк в качестве параметра и он называется data
. Он любит строки, потому что данные формы передаются в виде строк. Здесь нет никакой реальной работы, кроме как смотреть в коллекцию форм для элементов с ключом data
. Все элементы, которые соответствуют, добавляются в массив и назначаются вашему параметру.
Это работает, потому что параметр имеет одно имя как элементы формы. Если имена не совпадают, вы получите null, потому что ничего не найдено с этим именем.
Если вы используете сильные представления (представления с явной моделью), вы можете использовать помощники MVC для их создания для ваших, а элементам ввода будет присвоено собственное имя для возврата к вашему объекту.
Например, если у вас была модель:
public class BandMembers
{
public string[] data {get; set;}
}
И, на ваш взгляд, вы указали это как свою модель и использовали соответствующие HTML-помощники, ваш метод действий мог бы выглядеть следующим образом:
public ActionResult Create(BandMembers band)
{
//band now has a property called 'data' with 4 elements
return RedirectToAction("Index");
}
Это должно привести к созданию экземпляра объекта с именем band
, который имеет свойство names
с 4 значениями. Это работает, потому что модельное связующее видит параметр, называемый диапазоном, который не соответствует каким-либо известным ключам из коллекции форм, реализует его как сложный объект (а не строку, int, string [], int [] и т.д.) И исследует его члены. Он видит, что этот объект имеет строковый массив, называемый данными, и есть ключи в коллекции форм с этим именем. Он собирает значения, присваивает их свойству данных и назначает этот объект вашему параметру диапазона.
Теперь вы понимаете модели просмотра!
* Будьте осторожны, если бы вы использовали класс BandMembers в своем контроллере, но назвали его data
, вы получили бы нуль. Это связано с тем, что модельное связующее находит элементы в коллекции форм с ключом data
, но не может понять, как отбросить их из строк в объект BandMembers.
ИЗМЕНИТЬ
Что касается вашего редактирования с более глубоким примером, вот что я придумал.
Во-первых, моя модель просто так, что мы на одной странице. Я создал объект FormData, который содержит список разделов, чтобы действовать как совокупность объектов.
public class FormData
{
public List<Section> Sections { get; set; }
public FormData()
{
}
}
И мой класс Section.cs:
public class Section
{
public bool IsDeleted { get; set; }
public bool IsNew { get; set; }
public int Id { get; set; }
public int SectionOrder { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public Section()
{
}
}
Использование EditorTemplate в вашем разделе упрощает визуализацию содержимого с помощью генерируемых вами индексов. Я издевался над проектом самостоятельно и подтвердил, что это работает. К сожалению, как вы видели, после удаления элемента ваши индексы будут неработоспособными. Итак, как вы это исправите? Конечно, вы можете пойти и прочитать индексы и переписать их, ИЛИ - просто не удаляйте их! То, что я сделал в моем проекте mock, добавляет новое свойство в раздел, называемом IsDeleted, и делает его скрытым. В обработчике JavaScript для удаления нажмите, я скрываю строку и обновляю скрытый ввод для этой строки IsDeleted input в 'true'. Когда я отправлю форму, теперь у меня будет полная коллекция вместе с удобным флагом, который позволит мне узнать, какие строки мне нужно удалить из моей модели.
Я создал тестовое представление, связанное с моделью FormData, которая содержит список.
@model MVCEditorTemplateDemo.Models.FormData
@using (Html.BeginForm())
{
<table id="section-container">
@Html.EditorFor(m => m.Sections)
</table>
@Ajax.ActionLink("Add Section", "GetNewSection", "Home", new AjaxOptions() { HttpMethod="POST", InsertionMode=InsertionMode.InsertAfter, UpdateTargetId="section-container" })
<input type="submit" value="Submit" />
}
Да, редактор EditorFor берет коллекцию! Но как он знает, что с ним делать? Я создал папку в моем представлении/доме (может быть в Shared, если вы хотите использовать его через контроллеры), называемый EditorTemplates, в котором я помещаю частичный вид, называемый Section.cshtml. Имя важно - оно должно совпадать с именем объекта, который он будет отображать. Так как моя модель содержит объекты, называемые Section, мой EditorTemplate также следует называть секцией.
Вот как это выглядит (EditorTemplates\Section.cshtml):
@model MVCEditorTemplateDemo.Models.Section
<tr>
<td><a href="#" class="edit-section"><span class="glyphicon glyphicon-question-sign"></span></a></td>
<td>@Html.TextBoxFor(m => Model.SectionOrder, new { @class = "form-control" })</td>
<td>@Html.TextBoxFor(m => Model.Title, new { @class = "form-control" })</td>
<td>@Html.TextBoxFor(m => Model.SubTitle, new { @class = "form-control" })</td>
<td>
@Html.HiddenFor(m => Model.Id)
@Html.HiddenFor(m => Model.IsNew)
@Html.HiddenFor(m => Model.IsDeleted)
<a href="#" class="delete-section"><span class="glyphicon glyphicon-remove"></span></a>
</td>
</tr>
Я старался держать его как можно ближе к тому, что у вас было, чтобы соответствовать вашим требованиям. Обычно я бы не использовал таблицы, когда вы планируете динамически добавлять или удалять элементы. Некоторые браузеры не очень хорошо себя ведут, не говоря уже о том, что у моего дизайнера горячая ненависть к таблицам усложнений возникает при рендеринге.
Хорошо, теперь у вас есть то, что вам нужно, чтобы ASP.NET MVC автоматически отображал ваши объекты и автоматически генерировал индексы. Поэтому давайте посмотрим на удаление этой строки.
В моем тестовом представлении я добавил раздел сценариев следующим образом:
@section scripts
{
<script type="text/javascript">
$(function () {
$("table").on("click", ".delete-section", function() {
$(this).closest('tr').hide();
$(this).prev('input').val('true');
});
});
</script>
}
Это прекрасно работает. Когда пользователь нажимает кнопку "Удалить", они получают немедленную обратную связь от пользователя, что строка не прошла, и когда форма будет отправлена, у меня будет весь набор элементов, которые я предоставил с помощью удобного свойства, позволяющего мне знать, какие элементы мне нужно удалить из мое хранилище данных. Самая лучшая часть - мне никогда не приходилось перебирать мою коллекцию, и все мои индексы генерировались автоматически.
И это заканчивает мой ответ на ваш вопрос.
Но потом мне стало любопытно, что мне нужно сделать, чтобы создавать новые строки. Поэтому давайте взглянем на тестовое представление и на помощник Ajax.Action. Вы сразу заметите, что я поручаю браузеру выполнить запрос POST. Зачем? Поскольку браузеры могут кэшировать запросы GET для оптимизации производительности. Обычно вам небезразлично, поскольку вы обычно возвращаете один и тот же HTML для каждого запроса, но поскольку нам нужно включать специальное именование, наш HTML фактически каждый раз отличается (чтобы включить индекс в наши имена ввода). Остальное самоочевидно. Реальный трюк находится на стороне сервера - как мы можем вернуть часть, чтобы добавить строку в эту таблицу с надлежащей индексацией?
К сожалению, фреймворк, несмотря на то, что он нормально обнаруживает Views, кажется, падает при проверке в папках EditorTemplates и DisplayTemplates. Наше действие для этого немного грязнее, чем мы обычно имели, если бы мы не использовали шаблоны.
public ActionResult GetNewSection()
{
var section = new Section() { IsNew = true };
FakedData.Sections.Add(section);
ViewData.TemplateInfo.HtmlFieldPrefix = string.Format("Sections[{0}]", FakedData.Sections.Count-1);
return PartialView("~/Views/Home/EditorTemplates/Section.cshtml", section);
}
Хорошо, так что мы видим? Во-первых, я создаю новый объект Section, так как мне понадобится его для рендеринга EditorTemplate. Я добавил второе новое свойство IsNew, но на данный момент я на самом деле ничего не делаю. Мне просто нужен удобный способ увидеть, что было добавлено и удалено в моем методе POST.
Я добавляю этот новый раздел в мое хранилище данных (FakedData). Вместо этого вы можете отслеживать количество новых запросов по-другому - просто убедитесь, что он увеличивается каждый раз, когда вы нажимаете ссылку "Добавить раздел".
Теперь за трюк. Поскольку мы возвращаем частичное представление, у него нет контекста родительской модели. Если мы просто вернем шаблон только с переданным сектором, он не будет знать его часть большей коллекции и не будет соответствующим образом генерировать имена. Поэтому мы рассказываем, где мы находимся в поле HtmlFieldPrefix. Я использую свое хранилище данных для отслеживания нужного индекса, но опять же, это может произойти откуда угодно, и если вы добавите свойство IsNew, вы сможете добавить новые (и не удаленные) записи в свой магазин на submit вместо.
Как я уже сказал, структура имеет немного проблем с поиском шаблона только на основе имени, поэтому нам нужно указать путь к нему, чтобы он мог корректно возвращаться. Не очень много общего, но это немного раздражает.
Теперь я добавил обработчик POST для тестового представления, и я уверенно получаю количество удаленных элементов, их добавление и общее количество. Просто помните, что строки могут быть новыми и удалены!
[HttpPost]
public ActionResult Test(FormData form)
{
var sectionCount = form.Sections.Count();
var deletedCount = form.Sections.Count(i => i.IsDeleted);
var newItemCount = form.Sections.Count(i => i.IsNew);
form.Sections = form.Sections.Where(s => !s.IsDeleted).ToList();
FakedData = form;
return RedirectToAction("Test");
}
И что это. У нас есть полный сквозной вывод вашей коллекции с правильными индексами, "удаление" строк, добавление новых строк, и нам не пришлось взламывать привязку к модели, манипулировать именами или прибегать к хакам JavaScript для повторного набора наши элементы на submit.
Я с нетерпением жду ответа. Если я не вижу лучшего ответа, это может быть маршрут, который я всегда использую, когда делаю такие вещи в будущем.