2

在我的MedicalProductController中,我试图让我的Edit操作能够在一页上编辑多个对象。为此,我计划让我的HTTPPOST编辑操作方法接收一个,IEnumerable<MedicalProduct>而不是MedicalProduct脚手架为我设置的那个。

当我单击保存提交一些更改时,我得到一个ArguementNullException未处理的在线:_db.Entry(productList).State = EntityState.Modified;我不明白为什么它是空的。

医疗产品控制器:

public class MedicalProductController : Controller
{
    private MvcMedicalStoreDb _db = new MvcMedicalStoreDb();

    // some code omitted for brevity

    public ActionResult Edit(int id = 0)
    {
        MedicalProduct product = _db.Products.Find(id);
        if (product == null)
        {
            return HttpNotFound();
        }
        var productList = new List<MedicalProduct> { product }; 
        var viewModel = GetMedicalProductViewModelList(productList);
        return View(viewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(IEnumerable<MedicalProduct> productList)
    {
        if (ModelState.IsValid)
        {
            _db.Entry(productList).State = EntityState.Modified;
            _db.SaveChanges();
            return RedirectToAction("Index");
        }

        //var productList = new List<MedicalProduct> { product };
        var viewModel = GetMedicalProductViewModelList(productList);
        return View(viewModel);
    }

}

编辑.cshtml:

@model IEnumerable<MvcMedicalStore.Models.MedicalProductViewModel>

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>MedicalProduct</legend>

        @foreach (var modelItem in Model)
        {
            @Html.HiddenFor(item => modelItem.ID)

            <div class="editor-label">
                @Html.LabelFor(item => modelItem.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(item => modelItem.Name)
                @Html.ValidationMessageFor(item => modelItem.Name)
            </div>

            <div class="editor-label">
                @Html.LabelFor(item => modelItem.Price)
            </div>
            <div class="editor-field">
                @Html.EditorFor(item => modelItem.Price)
                @Html.ValidationMessageFor(item => modelItem.Price)
            </div>


            <div class="editor-label">
                @Html.LabelFor(item => modelItem.BrandName)
            </div>
            <div class="editor-field">
                @Html.DropDownListFor(item => modelItem.BrandName, modelItem.BrandSelectListItem)
                @Html.ValidationMessageFor(item => modelItem.BrandName)
            </div>
        }
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
4

1 回答 1

2

在我看来,模型绑定器无法绑定到您的集合,这将导致它成为null. 这样做的原因是您没有为每个元素指定索引。这意味着 MVC 无法确定如何正确绑定它们。

编辑

我已经弄清楚为什么这个答案的最新版本不起作用。首先,IEnumerable<T>没有直接索引器。相反,您将使用Model.ElementAt(i).ID来访问该ID属性。但是,这实际上并不能解决模型绑定问题,因为出于某种原因,这不会在生成的字段的name属性上生成正确的索引。<input>(更多内容如下。)

有两种方法可以解决这个问题。第一种方法是将 a 传递List给视图,而不是IEnumerable,然后访问我之前展示的字段。但是,更好的方法是创建一个EditorTemplate。这将更容易,因为它使您不必更改生成视图模型的现有方法。因此,您需要按照以下步骤操作:

  1. EditorTemplates在视图的当前文件夹内创建一个文件夹(例如,如果您的视图是Home\Index.cshtml,则创建文件夹Home\EditorTemplates)。
  2. 在该目录中创建一个名称与您的模型匹配的强类型视图(例如,在这种情况下,视图将被称为MedicalProductViewModel)。
  3. 将大部分原始视图移动到该新模板中。

你最终会得到以下结果:

@model MedicalProductViewModel

@Html.HiddenFor(item => Model.ID)

<div class="editor-label">
    @Html.LabelFor(item => Model.Name)
</div>
<div class="editor-field">
    @Html.EditorFor(item => Model.Name)
    @Html.ValidationMessageFor(item => Model.Name)
</div>

<div class="editor-label">
    @Html.LabelFor(item => Model.Price)
</div>
<div class="editor-field">
    @Html.EditorFor(item => Model.Price)
    @Html.ValidationMessageFor(item => Model.Price)
</div>

<div class="editor-label">
    @Html.LabelFor(item => Model.BrandName)
</div>
<div class="editor-field">
    @Html.DropDownListFor(item => Model.BrandName, Model.BrandSelectListItem)
    @Html.ValidationMessageFor(item => Model.BrandName)
</div>

请注意我们如何不再使用任何索引符号来访问模型属性。

现在在你Edit.cshtml看来,你会得到这个:

@model IEnumerable<MvcMedicalStore.Models.MedicalProductViewModel>

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>MedicalProduct</legend>
        @Html.EditorFor(m => m)
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

尽管我在开始时给出了简短的解释,但我应该真正解释一下这实际上是在做什么。您的原始 HTML 会产生如下输出:

<input name="ID" type="text" value="1" />
<input name="Name" type="text" value="Name 1" />
<input name="ID" type="text" value="2" />
<input name="Name" type="text" value="Name 2" />

如您所见,多个输入字段共享相同的名称。这就是模型绑定器出错的原因,因为您的操作是告诉它绑定到一个集合,并且绑定器需要能够区分集合中的每个元素。 EditorTemplates足够聪明,可以确定您何时使用集合,并将索引自动应用于您的输入字段。上面的代码将生成这样的输出:

<input name="[0].ID" type="text" value="1" />
<input name="[0].Name" type="text" value="Name 1" />
<input name="[1].ID" type="text" value="2" />
<input name="[1].Name" type="text" value="Name 2" />

如您所见,这些字段现在有一个与之关联的索引。这为模型绑定器提供了将所有项目添加到集合所需的所有信息。现在已经解决了,我们可以回去修复您的产品保存代码。

Gert 所说的关于您尝试保存的方式仍然是正确的productList。您需要EntityState.Modified在该集合中的每个单独项目上设置标志:

if (ModelState.IsValid)
{
    foreach (var product in productList)
        _db.Entry(product).State = EntityState.Modified;

    _db.SaveChanges();

    return RedirectToAction("Index");
}

看看这是否有效。

于 2013-10-01T18:21:23.443 回答