TL; DR : в моем приложении ASP.NET MVC3, как мне реализовать представление, которое позволяет мне редактировать детали «родительской» сущности одновременно с деталями списка «детские» сущности?
Обновление : я принимаю @ torm's ответ , потому что он предоставил ссылку , которая дает некоторое объяснение того, почему мое текущее решение может быть Лучше не бывает. Тем не менее, мы хотели бы услышать, есть ли у кого-нибудь еще альтернатива!
Я искал и читал (см. Раздел «Список литературы» внизу, где приведены некоторые выводы).
Тем не менее, я все еще чувствую, что есть что-то «вонючее» с решениями, которые я нашел до сих пор. Интересно, есть ли у кого-нибудь из вас более элегантный ответ или предложение (или вы можете объяснить, почему это может быть «настолько хорошо, насколько это возможно»). Заранее спасибо!
Итак, вот настройки:
Модели:
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);
}
/* ... */
}
The View: Views \ 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>
Что происходит?
При настройке выше генерируется страница со стандартными элементами ввода для модели «родительского» списка желаний:
<input class="text-box single-line" id="Name" name="Name" type="text" value="MyWishlist" />
Для «детских» пожеланий в таблице мы получаем индексированные элементы ввода:
<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
, возвращенному с пустым свойством .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);
}
}
Мой список желаний
Хотелось бы, чтобы у меня был способ получить все POST-данные в одном структурированном объекте, например:
[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.
- Я не использую '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 - пакет обновления 1 для ENU (KB983509)
- .NET Framework 4.0.30319 SP1Rel
- ASP.NET MVC3
- Синтаксис бритвы для видов
- Код-Первый подход
- Entity Framework 4.2.0.0
- Песочница построена для .NET Framework 4
Ссылки:
«Начало работы с ASP.NET MVC3»
Охватывает основы, но не касается модельных отношений
«Начало работы с EF с использованием MVC»
ан-Asp-нетто-MVC-приложения
В частности Часть 6 показывает, как справляться с некоторыми отношениями между моделями.
Однако в этом руководстве для обработчика POST используется аргумент FormCollection
, а не автоматическое связывание модели.
Другими словами:
[HttpPost] public ActionResult Edit (int id, FormCollection formCollection)Вместо того, чтобы что-то вроде публичного редактирования [HttpPost] ActionResult (InstructorAndCoursesViewModel viewModel) Кроме того, список курсов, связанных с данным инструктором, представлен (в пользовательском интерфейсе) в виде набора флажков с тем же именем (что приводит к * 1121).* аргумент для обработчика POST), не совсем тот сценарий, на который я смотрю.
"Редактирование списка переменной длины в стиле ASP.NET MVC2" Основано на MVC2 (поэтому мне интересно, если он все еще описывает лучший вариант, когда у нас есть MVC3).По общему признанию, я (еще) не имел дело со вставками и / или удалением моделей Children из списка.Кроме того, это решение:
- опирается на пользовательский код (BeginCollectionItem) - что хорошо, если это необходимо (но все же необходимо в MVC3?)
- обрабатывает список какавтономная коллекция, а не свойство модели обтекания - другими словами, существует окружающая модель "GiftsSet" (эквивалентная родительской модели Wishlist в моем примере), хотя я не знаю, делает ли недопустимым введение явной родительской моделиэто решение или нет.
"Формат провода ASP.NET для привязки моделей к массивам, спискам, коллекциям, словарям" Пост Скотта Хансельмана является одним изсамая цитируемая ссылка на тему привязки к спискам в приложениях 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
).Это может быть или не быть критическим в примере, но, вероятно, будет проблемой в моем реальном приложении, где разные «параллельные» списки детей могут отображаться в одном и том же виде.