Привязка редактируемого списка детей

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);
    }
    /* ... */
}

Представление: Views\Wishlist\Edit.cshtml

@model Sandbox.Models.Wishlist

Edit

@using (Html.BeginForm()) { @Html.ValidationSummary(true)
Wishlist @Html.HiddenFor(model => model.WishListId)
@Html.LabelFor(model => model.Name)
@Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name)
@for (var itemIndex = 0; itemIndex < Model.Wishitems.Count; itemIndex++) { @Html.EditorFor(item => Model.Wishitems.ToList()[itemIndex]) }
Quantity Name

}

Шаблон редактора: Views\Shared\EditorTemplates\Wishitem.cshtml

@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 wishitems)) по-прежнему получает пустой 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... */
}

Подсказки, советы, мысли?

Примечания:

  • Это пример из песочницы. Реальное приложение, над которым я работаю, совсем другое и не имеет ничего общего с доменом, представленным в Sandbox.
  • Я не использую 'ViewModels' в примере, потому что - пока что - они не кажутся частью ответа. Если они будут необходимы, я, конечно, введу их (и в реальном приложении, над которым я работаю, мы уже используем их).
  • Аналогично, хранилище абстрагировано простым классом SandboxDbContext в этом примере, но в реальном приложении, вероятно, будет заменено общим шаблоном Repository and Unit Of Work.
  • Приложение Sandbox создано с использованием:
    • Visual Web Developer 2010 Express
      • Hotfix for Microsoft Visual Web Developer 2010 Express - ENU (KB2547352)
      • Hotfix for Microsoft Visual Web Developer 2010 Express - ENU (KB2548139)
      • Microsoft Visual Web Developer 2010 Express - ENU Service Pack 1 (KB983509)
    • .NET Framework 4.0.30319 SP1Rel
    • ASP.NET MVC3
      • Razor синтаксис для Views
      • Code-First подход
    • Entity Framework 4.2.0.0
  • Sandbox построен с учетом .NET Framework 4

Ссылки:

  • "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 из списка. Кроме того, это решение:

    • полагается на пользовательский код (BeginCollectionItem) - что хорошо, если это необходимо (но так ли это необходимо в MVC3?)
    • обрабатывает список как отдельную коллекцию, а не как свойство оберточной модели - другими словами, существует окружающая модель "GiftsSet" (эквивалентная родительской модели Wishlist в моем примере), хотя я не знаю, введение явной родительской модели делает это решение недействительным или нет.
  • "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). Это может быть или не быть критичным в примере, но, вероятно, будет проблемой в моем реальном приложении, где различные "параллельные" списки дочерних элементов могут появляться в одном представлении.

10
задан Community 23 May 2017 в 12:06
поделиться