Привязка редактируемого списка детей - PullRequest
10 голосов
/ 20 декабря 2011

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).Это может быть или не быть критическим в примере, но, вероятно, будет проблемой в моем реальном приложении, где разные «параллельные» списки детей могут отображаться в одном и том же виде.

1 Ответ

1 голос
/ 20 декабря 2011

Вы можете проверить эту ссылку http://www.codetuning.net/blog/post/Binding-Model-Graphs-with-ASPNETMVC.aspx У меня была похожая проблема, и вышеизложенное позволило мне понять ее

...