绑定可编辑的子项列表

FOR*_*FOR 10 model-binding asp.net-mvc-3

TL; DR:在我的ASP.NET MVC3应用程序中,我应该如何实现一个允许我在"子"实体列表的详细信息的同时编辑"父"实体的详细信息的视图?

更新:我接受了@ torm的答案,因为他提供了一个链接,可以解释为什么我当前的解决方案可能会有所改善.但是,如果有其他人有任何选择,我们很乐意听到!

我一直在寻找和阅读(到目前为止的一些调查结果,请参见底部的"参考文献"部分).然而,我仍然觉得到目前为止我找到的解决方案有点"臭".我想知道你们中是否有人有更优雅的答案或建议(或者可以解释为什么这可能会"变得如此好").提前致谢!

所以,这是设置:

型号:

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; }
}
Run Code Online (Sandbox Code Playgroud)

控制者:

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);
    }
    /* ... */
}
Run Code Online (Sandbox Code Playgroud)

视图:视图\ 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>
}
Run Code Online (Sandbox Code Playgroud)

编辑器模板: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>
Run Code Online (Sandbox Code Playgroud)

到底是怎么回事?

上面的设置生成一个页面,其中包含"父"愿望清单模型的标准输入元素:

<input class="text-box single-line" id="Name" name="Name" type="text" value="MyWishlist" />  
Run Code Online (Sandbox Code Playgroud)

对于表中的'children'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" />
Run Code Online (Sandbox Code Playgroud)

这导致了一个Wishlist wishlist带有空.Wishitems属性的POST参数.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我的收藏

我希望有一种方法可以将所有POSTed数据放在一个结构化对象中,例如:

[HttpPost]
public ActionResult Edit(Wishlist wishlist) { /* ...Save the wishlist... */ }
Run Code Online (Sandbox Code Playgroud)

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... */
}
Run Code Online (Sandbox Code Playgroud)

提示,提示,想法?

笔记:

  • 这是一个沙盒示例.我正在处理的实际应用程序是完全不同的,与Sandbox中公开的域无关.
  • 我在示例中没有使用"ViewModels",因为 - 到目前为止 - 它们似乎不是答案的一部分.如果它们是必要的,我肯定会介绍它们(在我正在研究的真实应用程序中,我们已经在使用它们).
  • 类似地,在此示例中,存储库由简单的SandboxDbContext类抽象,但可能会被真实应用程序中的通用存储库和工作单元模式替换.
  • 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 Service Pack 1(KB983509)
    • .NET Framework 4.0.30319 SP1Rel
    • ASP.NET MVC3
      • 视图的Razor语法
      • 代码优先方法
    • 实体框架4.2.0.0
  • Sandbox是针对.NET Framework 4构建的

参考文献:

  • "ASP.NET MVC3入门" 涵盖了基础知识,但不涉及模型关系

  • "使用MVC入门EF" an-asp-net-mvc-application特别是第6部分展示了如何处理模型之间的一些关系.但是,本教程使用FormCollectionPOST处理程序的参数,而不是自动模型绑定.换句话说:[HttpPost] public ActionResult Edit(int id,FormCollection formCollection)而不是[HttpPost]行的内容公共ActionResult编辑(InstructorAndCoursesViewModel viewModel)此外,还表示与给定教师关联的课程列表(在UI)作为一组具有相同名称的复选框(导致string[]POST处理程序的参数),与我正在查看的场景不完全相同.

  • "编辑一个可变长度列表,ASP.NET MVC2风格" 基于MVC2(所以我想知道它是否仍然描述了我们拥有MVC3的最佳选择).不可否认,我还没有(还)处理从名单中插入和/或删除儿童模型的问题.此外,这个解决方案:

    • 依赖于自定义代码(BeginCollectionItem) - 如果有必要,这很好(但在MVC3中是否仍然需要?)
    • 处理列表作为一个独立的集合,而不是包装模型的属性 - 换句话说,有周围的"GiftsSet"模型(相当于我的例子中的父愿望清单模型),虽然我不知道是否介绍显式父模型是否使此解决方案无效.
  • "用于模型绑定到数组,列表,集合,词典的ASP.NET线格式" Scott Hanselman的帖子是MVC应用程序中绑定到列表主题的最引用的参考文章之一.但是,他只是简单地描述框架采用的命名约定,并用于生成与您的操作方法匹配的对象(请注意文章没有生成页面的示例,然后将数据提交到所描述的操作之一).如果我们必须自己生成HTML,这是很好的信息.我们必须吗?

  • "模型绑定到列表" 另一个顶级参考,由Phil Haack.它有一些与上面Hansleman帖子相同的信息,但也向我们展示了我们可以在循环(for (int i = 0; i < 3; i++) { Html.TextBoxFor(m => m[i].Title) })或编辑器模板(Html.EditorFor(m=>m[i]))中使用HtmlHelpers .但是,使用此方法,编辑器模板生成的HTML将不包含任何特定前缀(例如:输入元素的名称和ID将采用以下形式[index].FieldName:[0].Quantity,或[1].Name).这在示例中可能或可能不是关键,但在我的实际应用中可能是一个问题,其中不同的"并行"子列表可能出现在同一视图中.