TL; DR: В моем приложении ASP.NET MVC3, как мне реализовать представление, которое позволяет мне редактировать детали родительского объекта одновременно с деталями списка дочерних объектов?
Обновить: я принимаю @torm answer, потому что он предоставил ссылка, которая дает некоторое объяснение, почему мое текущее решение может быть таким же хорошим, как и оно. Однако нам бы хотелось услышать, есть ли у кого-нибудь альтернатива!
Я искал и читал (см. раздел "Ссылки" внизу для некоторых из полученных до сих пор результатов). Тем не менее, я все еще чувствую, что есть что-то "вонючее" с решениями, которые я нашел до сих пор. Интересно, есть ли у кого-нибудь из вас более элегантный ответ или предложение (или можно объяснить, почему это может быть "так хорошо, как это получается" ). Спасибо заранее!
Итак, здесь настройка:
Модели:
public class Wishlist
{
public Wishlist() { Wishitems = new List<Wishitem>(); }
public long WishListId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Wishitem> 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<Wishitem> wishitems)
{
if (ModelState.IsValid)
{
db.Entry(wishlist).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(wishlist);
}
/* ... */
}
Вид: Представления \Wishlist\Edit.cshtml
@model Sandbox.Models.Wishlist
<h2>Edit</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<fieldset>
<legend>Wishlist</legend>
@Html.HiddenFor(model => model.WishListId)
<div class="editor-label">@Html.LabelFor(model => model.Name)</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<table>
<tr>
<th>
Quantity
</th>
<th>
Name
</th>
</tr>
@for (var itemIndex = 0; itemIndex < Model.Wishitems.Count; itemIndex++)
{
@Html.EditorFor(item => Model.Wishitems.ToList()[itemIndex])
}
</table>
<p>
<input type="submit" value="Save" />
</p>
}
Шаблон редактора: Views\Shared\EditorTemplates\Wishitem.cshtml
@model Sandbox.Models.Wishitem
<tr>
<td>
@Html.HiddenFor(item=>item.WishitemId)
@Html.TextBoxFor(item => item.Quantity)
@Html.ValidationMessageFor(item => item.Quantity)
</td>
<td>
@Html.TextBoxFor(item => item.Name)
@Html.ValidationMessageFor(item => item.Name)
</td>
</tr>
Что происходит?
В приведенной выше настройке создается страница со стандартными элементами ввода для "родительской" модели Wishlist:
<input class="text-box single-line" id="Name" name="Name" type="text" value="MyWishlist" />
Для "детей" Wishitems в таблице мы получаем индексированные элементы ввода:
<input data-val="true" data-val-number="The field Quantity must be a number." data-val-required="The Quantity field is required." name="[0].Quantity" type="text" value="42" />
<input name="[0].Name" type="text" value="Unicorns" />
Это приводит к аргументу Wishlist wishlist
POSTed назад с пустым свойством .Wishitems
.
Альтернативная сигнатура для обработчика POST ([HttpPost] public ActionResult Edit(Wishlist wishlist, ICollection<Wishitem> wishitems)
) по-прежнему получает пустой wishlist.Wishitems
, но позволяет мне получить доступ к (потенциально измененному) wishitems
.
В этом втором сценарии я могу сделать некоторые для пользовательской привязки. Например (не самый элегантный код, который я видел в своей карьере):
[HttpPost]
public ActionResult Edit(Wishlist editedList, ICollection<Wishitem> 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<Wishitem> 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... */
}
Советы, подсказки, мысли?
Примечания:
- Это пример Sandbox. Фактическое приложение, над которым я работаю, совсем другое, не имеет ничего общего с доменом, открытым в Sandbox.
- Я не использую "ViewModels" в этом примере, потому что, да еще, они, похоже, не являются частью ответа. Если они понадобятся, я бы обязательно их представил (и в реальном приложении, над которым я работаю, мы уже используем их).
- Аналогично, репозиторий абстрагируется простым классом SandboxDbContext в этом примере, но, вероятно, будет заменен общим шаблоном Repository и Unit Of Work в реальном приложении.
- Приложение Sandbox построено с использованием:
- Visual Web Developer 2010 Express
- Исправление для Microsoft Visual Web Developer 2010 Express - ENU (KB2547352)
- Исправление для Microsoft Visual Web Developer 2010 Express - ENU (KB2548139)
- Microsoft Visual Web Developer 2010 Express - ENU с пакетом обновления 1 (KB983509)
- .NET Framework 4.0.30319 SP1Rel
- ASP.NET MVC3
- Синтаксис Razor для представлений
- Метод Code-First
- Entity Framework 4.2.0.0
- Visual Web Developer 2010 Express
- Песочница построена с таргетингом на .NET Framework 4
Литература:
-
"Начало работы с ASP.NET MVC3" Обнаруживает основы, но не имеет отношения к отношениям модели.
-
"Начало работы с EF с помощью MVC" ан-Asp-нетто-MVC-приложения В частности, Часть 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). По общему признанию, мне еще не удалось разобраться с вставками и/или удалением моделей "Дети" из списка. Кроме того, это решение:
- полагается на пользовательский код (BeginCollectionItem) - это нормально, если это необходимо (но все же это необходимо в MVC3?)
- обрабатывает список как самостоятельную коллекцию, а не свойство модели обертывания - другими словами, существует окружающая модель "GiftsSet" (эквивалентная исходной модели Wishlist в моем примере), хотя я не знаете, если введение явной родительской модели недействительно это решение или нет.
-
Формат проводки ASP.NET для привязки к массивам, спискам, коллекциям, словарям Сообщение Scott Hanselman является одной из наиболее цитируемых ссылок на тему привязки к спискам в приложениях MVC. Однако он просто описывает соглашения об именах, принятые каркасом, и используется для создания объектов, соответствующих вашему методу действий (обратите внимание, как в статье нет примера создания страницы, которая затем передает данные в одно из описанных действий). Это отличная информация, если мы должны сами создавать HTML. Мы должны?
-
"привязка модели к списку" Еще одна главная ссылка, Фил Хаак. Он содержит некоторую информацию, такую как сообщение Hansleman выше, но также показывает, что мы можем использовать HtmlHelpers в цикле (
for (int i = 0; i < 3; i++) { Html.TextBoxFor(m => m[i].Title) }
) или в шаблоне редактора (Html.EditorFor(m=>m[i])
). Однако, используя этот подход, HTML, созданный шаблоном редактора, не будет содержать какого-либо конкретного префикса (например: имена и идентификаторы входных элементов будут в форме[index].FieldName
, например:[0].Quantity
или[1].Name
). Это может быть или не быть критичным в этом примере, но, вероятно, будет проблемой в моем реальном приложении, где в этом же представлении могут отображаться разные "параллельные" списки детей.