Вы можете это сделать абсолютно, но люди изначально имеют тенденцию сталкиваться с проблемами, поэтому я приведу полный пример.
Для начала давайте определим HomeController
, который имеет Index
действие, которое возвращает List<EmployeeViewModel>
:
public class HomeController : Controller
{
public ActionResult Index()
{
// Assume this would really come from your database.
var employees = new List<EmployeeViewModel>()
{
new EmployeeViewModel { Id = 1, Name = "Employee 1" },
new EmployeeViewModel { Id = 2, Name = "Employee 2" },
new EmployeeViewModel { Id = 3, Name = "Employee 3" },
};
return View(employees);
}
[HttpPost]
public ActionResult Index(List<EmployeeViewModel> employees)
{
// Rest of action
}
}
Типичными как люди первым подойти к этой проблеме является сделать что-то вроде следующего в index.cshtml
:
@model List<EmployeeViewModel>
@using (Html.BeginForm())
{
foreach (var item in Model)
{
<div class="row">
@Html.EditorFor(x => item.Id)
</div>
<div class="row">
@Html.EditorFor(x => item.Name)
</div>
}
<input type="submit" />
}
На первый взгляд это выглядело бы так, как будто это сработает. Однако, если вы поместите контрольную точку в действие POST и нажмите кнопку отправки, вы заметите, что employees
- null
. Причина этого в том, что цикл foreach
генерирует HTML вроде следующего:
<input name="item.Id" type="number" value="1" />
<input name="item.Name" type="text" value="Employee 1" />
<input name="item.Id" type="number" value="2" />
<input name="item.Name" type="text" value="Employee 2" />
<input name="item.Id" type="number" value="3" />
<input name="item.Name" type="text" value="Employee 3" />
Я раздели некоторые ненужные части, но обратите внимание, как name
атрибуты имеют одинаковые значения для каждого сотрудника. Когда связующее устройство по умолчанию пытается создать список данных на сервере, оно должно быть способным различать разных сотрудников, и, поскольку оно не может, оно приводит к тому, что в списке есть null
.
Так почему же он генерирует одни и те же значения? Именно из-за этого:
@Html.EditorFor(x => item.Id)
@Html.EditorFor(x => item.Name)
Мы не передавая значение индекса для метода с HtmlHelper
вызовов, так что информация не делает его к сгенерированной HTML. Мы можем исправить это, просто сделав использование for
петли вместо:
for (int i = 0; i < Model.Count; i++)
{
@Html.EditorFor(x => Model[i].Id)
@Html.EditorFor(x => Model[i].Name)
}
Как мы теперь поставляем индекс с каждым вызовом метода, сгенерированный HTML делает сейчас содержит индекс для каждого сотрудника:
<input name="[0].Id" type="number" value="1" />
<input name="[0].Name" type="text" value="Employee 1" />
<input name="[1].Id" type="number" value="2" />
<input name="[1].Name" type="text" value="Employee 2" />
<input name="[2].Id" type="number" value="3" />
<input name="[2].Name" type="text" value="Employee 3" />
Это позволяет связующему объекту по умолчанию связывать Id
и Name
с каждым EmployeeViewModel
, что позволяет ему правильно построить тип на сервере.
На данный момент ваша проблема решена, однако не рекомендуется использовать цикл for
, если вы можете избежать этого, что приводит нас к шаблонам редактора. Шаблоны редактора - это HtmlHelper
методы, которые позволяют нам отображать настраиваемый шаблон (т. Е. Представление) для заданного типа. Поэтому позвольте мне показать вам пример того, как это сделать с приведенным выше примером.
Для начала, вам необходимо сделать следующее:
- Создать
EditorTemplates
папку внутри папки ~/Views/Home/
(имя EditorTemplates
имеет особое значение в MVC, поэтому очень важно, чтобы записать это правильно) ,
- Создайте вид
EmployeeViewModel.cshtml
внутри этой папки (опять же, здесь важно имя: оно должно совпадать с именем типа, для которого вы хотите создать собственный шаблон).
После того, как вы сделали это, он должен выглядеть следующим образом:
Теперь откройте EmployeeViewModel.cshtml
, и поместите код визуализации из Index.cs
внутри него:
@model EmployeeViewModel
<div class="row">
@Html.EditorFor(x => x.Id)
</div>
<div class="row">
@Html.EditorFor(x => x.Name)
</div>
И, наконец, откройте index.cshtml
и измените его на следующее:
@model List<EmployeeViewModel>
@using (Html.BeginForm())
{
@Html.EditorFor(x => x)
<input type="submit" />
}
Html.EditorFor
, и Html.DisplayFor
, оба достаточно умны, чтобы понять, когда они называют для коллекции, так что они выглядят для пользовательского шаблона для визуализации типа коллекции (в данном случае, EditorFor
будет искать шаблон редактора для EmployeeViewModel
).
Поскольку мы предоставили шаблон редактора, он не только отобразит его для каждого элемента в коллекции, но также будет генерировать правильные индексы для каждого из этих элементов, предоставляя модели связывать всю необходимую ему информацию для восстановления эта коллекция на сервере.
Конечный результат заключается в том, что привязка к модели упрощается, а не только код для ваших представлений проще, он также разбивается на основе задействованных типов, что упрощает работу с вашими представлениями, а не с гигантский вид, который делает все.
Следует также упомянуть, что, поскольку мой пример непосредственно используется в коллекции, и ничего более, вы можете фактически заменить @Html.EditorFor(x => x)
на @Html.EditorForModel()
. Я не делал этого изначально, так как не хотел создавать впечатление, что шаблоны вызываются только с использованием последнего.
О, ничего себе! Огромное спасибо!! –
@HarporSydney Добро пожаловать. :) –