TL;DR: В моем приложении ASP.NET MVC3 App, как я должен реализовать представление, которое позволяет мне редактировать детали "родительской" сущности одновременно с деталями списка "дочерних" сущностей?
Обновление: Я принимаю ответ @torm, потому что он предоставил ссылку, которая дает некоторое объяснение, почему мое текущее решение может быть настолько хорошим, насколько это возможно. Тем не менее, мы будем рады услышать, если у кого-то еще есть альтернатива!
Я искал и читал (см. раздел "Ссылки" внизу для ознакомления с некоторыми выводами на данный момент). Однако, я все еще чувствую, что есть что-то "вонючее" в тех решениях, которые я нашел до сих пор. Интересно, есть ли у кого-нибудь из вас более элегантный ответ или предложение (или вы можете объяснить, почему это может быть "как нельзя лучше")? Заранее спасибо!
Итак, вот установка:
public class Wishlist
{
public Wishlist() { Wishitems = new List(); }
public long WishListId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection Wishitems { get; set; }
}
public class Wishitem
{
public long WishitemId { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
public class WishlistsController : Controller
{
private SandboxDbContext db = new SandboxDbContext();
/* ... */
public ActionResult Edit(long id)
{
Wishlist wishlist = db.Wishlists.Find(id);
return View(wishlist);
}
[HttpPost]
public ActionResult Edit(Wishlist wishlist)
//OR (see below): Edit(Wishlist wishlist, ICollection wishitems)
{
if (ModelState.IsValid)
{
db.Entry(wishlist).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(wishlist);
}
/* ... */
}
@model Sandbox.Models.Wishlist
Edit
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
Quantity
Name
@for (var itemIndex = 0; itemIndex < Model.Wishitems.Count; itemIndex++)
{
@Html.EditorFor(item => Model.Wishitems.ToList()[itemIndex])
}
}
@model Sandbox.Models.Wishitem
@Html.HiddenFor(item=>item.WishitemId)
@Html.TextBoxFor(item => item.Quantity)
@Html.ValidationMessageFor(item => item.Quantity)
@Html.TextBoxFor(item => item.Name)
@Html.ValidationMessageFor(item => item.Name)
Приведенная выше установка создает страницу со стандартными элементами ввода для "родительской" модели Wishlist:
Для "дочерних" Wishitems в таблице мы получаем индексированные элементы ввода:
Это приводит к тому, что аргумент Wishlist wishlist
POSTed обратно с пустым свойством .Wishitems
.
Альтернативная сигнатура для обработчика POST ([HttpPost] public ActionResult Edit(Wishlist wishlist, ICollection
) по-прежнему получает пустой wishlist.Wishitems
, но позволяет мне получить доступ к (потенциально измененным) wishitems
.
В этом втором сценарии я могу сделать некоторое пользовательское связывание. Например (не самый элегантный код, который я видел за свою карьеру):
[HttpPost]
public ActionResult Edit(Wishlist editedList, ICollection editedItems)
{
var wishlist = db.Wishlists.Find(editedList.WishListId);
if (wishlist == null) { return HttpNotFound(); }
if (ModelState.IsValid)
{
UpdateModel(wishlist);
foreach (var editedItem in editedItems)
{
var wishitem = wishlist.Wishitems.Where(wi => wi.WishitemId == editedItem.WishitemId).Single();
if (wishitem != null)
{
wishitem.Name = editedItem.Name;
wishitem.Quantity = editedItem.Quantity;
}
}
db.SaveChanges();
return View(wishlist);
}
else
{
editedList.Wishitems = editedItems;
return View(editedList);
}
}
Я бы хотел, чтобы был способ получить все POSTed данные в одном структурированном объекте, например:
[HttpPost]
public ActionResult Edit(Wishlist wishlist) { /* ...Save the wishlist... */ }
С wishlist.Wishitems
, заполненным (потенциально измененными) элементами
Или более элегантный способ для меня обработать объединение данных, если мой контроллер должен получать их отдельно. Что-то вроде
[HttpPost]
public ActionResult Edit(Wishlist editedList, ICollection editedItems)
{
var wishlist = db.Wishlists.Find(editedList.WishListId);
if (wishlist == null) { return HttpNotFound(); }
if (ModelState.IsValid)
{
UpdateModel(wishlist);
/* and now wishlist.Wishitems has been updated with the data from the Form (aka: editedItems) */
db.SaveChanges();
return View(wishlist);
}
/* ...Etc etc... */
}
Подсказки, советы, мысли?
"Getting Started with ASP.NET MVC3" Охватывает основы, но не рассматривает отношения моделей
"Getting Started with EF using MVC"
an-asp-net-mvc-application .
В частности, Часть 6 показывает, как работать с некоторыми отношениями между моделями.
Однако в этом руководстве используется аргумент FormCollection
для обработчика POST, а не автоматическое связывание моделей.
Другими словами:
[HttpPost] public ActionResult Edit(int id, FormCollection formCollection)
Вместо того, чтобы написать что-то вроде
[HttpPost] public ActionResult Edit(InstructorAndCoursesViewModel viewModel)
Кроме того, список курсов, связанных с данным инструктором, представлен (в пользовательском интерфейсе) как набор флажков с тем же именем (что приводит к string[]
аргументу для обработчика POST), не совсем тот же сценарий, который я рассматриваю.
"Редактирование списка переменной длины, ASP.NET MVC2-style". Основано на MVC2 (поэтому мне интересно, является ли это описание лучшим вариантом теперь, когда у нас есть MVC3). Признаться, я еще (пока) не разобрался со вставками и/или удалением моделей Children из списка. Кроме того, это решение:
"ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries" Сообщение Скотта Хансельмана является одной из наиболее цитируемых ссылок на тему привязки к спискам в приложениях MVC. Однако он просто описывает соглашения об именовании, принятые фреймворком и используемые для генерации объектов, соответствующих методу вашего действия (обратите внимание, что в статье нет примера генерации страницы, которая затем отправляет данные одному из описанных действий). Это отличная информация, если нам придется генерировать HTML самостоятельно. А нужно ли?
"Model Binding To A List".
Еще одна лучшая ссылка, автор Phil Haack. Она содержит ту же информацию, что и пост Hansleman выше, но также показывает, что мы можем использовать HtmlHelpers в цикле (for (int i = 0; i m[i].Title) }
) или в шаблоне редактора (Html.EditorFor(m=>m[i])
).
Однако при таком подходе HTML, генерируемый шаблоном редактора, не будет включать какой-либо конкретный префикс (например, имена и идентификаторы элементов ввода будут иметь вид [index].FieldName
, как: [0].Quantity
, или [1].Name
). Это может быть или не быть критичным в примере, но, вероятно, будет проблемой в моем реальном приложении, где различные "параллельные" списки дочерних элементов могут появляться в одном представлении.