渲染的局部视图与模型不匹配

所以我编写了一些代码,允许使用AJAX在ASP.NET MVC中动态添加和删除集合中的元素。 向集合添加新项目按预期工作,但删除不会。 模型集合按预期更新(通过索引删除相应的项目),但呈现的HTML始终显示已删除最后一个项目(而不是指定索引处的项目)。

例如,假设我有以下项目:

  • 酒吧
  • 巴兹

当我点击名为“Foo”的项目旁边的“删除”时,我希望生成的呈现HTML看起来如下:

  • 酒吧
  • 巴兹

当我通过控制器操作进行调试时,似乎就是这种情况,因为模型上的Names集合仅包含这些项目。 但是,返回给我的AJAX处理程序的呈现HTML是:

  • 酒吧

我认为这个问题可能与缓存有关,但我没有尝试过(OutputCache指令,在$ .ajax中设置cache:false等)。

这是代码:

DemoViewModel.cs

namespace MvcPlayground.Models { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public class DemoViewModel { public List Names { get; set; } public DemoViewModel() { Names = new List(); } } } 

DemoController.cs

这里明显的问题是RemoveName方法。 我可以validationPartialViewResult的Model属性是否像我期望的那样反映了集合状态,但是一旦呈现给客户端,HTML就不像我期望的那样。

 namespace MvcPlayground.Controllers { using MvcPlayground.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; public class DemoController : Controller { // GET: Demo public ActionResult Index() { var model = new DemoViewModel(); return View(model); } [HttpPost] public ActionResult AddName(DemoViewModel model) { model.Names.Add(string.Empty); ViewData.TemplateInfo.HtmlFieldPrefix = "Names"; return PartialView("EditorTemplates/Names", model.Names); } [HttpPost] public ActionResult RemoveName(DemoViewModel model, int index) { model.Names.RemoveAt(index); ViewData.TemplateInfo.HtmlFieldPrefix = "Names"; var result = PartialView("EditorTemplates/Names", model.Names); return result; } } } 

Names.cshtml

这是我用来呈现名称列表的编辑器模板。 在向集合中添加新项目时按预期工作。

 @model List @for (int i = 0; i < Model.Count; i++) { 

@Html.EditorFor(m => m[i]) @Html.ActionLink("remove", "RemoveName", null, new { data_target = "names", data_index = i, @class = "link link-item-remove" })

}

Index.cshtml

这是加载的初始页面,这里没什么太复杂的。

 @model MvcPlayground.Models.DemoViewModel @{ ViewBag.Title = "Index"; } 

Index

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

Demo


@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.EditorFor(m => m.Names, "Names")
@Html.ActionLink("Add New", "AddName", "Demo", null, new { data_target = "names", @class = "btn btn-addnew" })
}

Index.js

此脚本处理对AddName和RemoveName的调用。 这里的一切都像我期望的那样有效。

 $('form').on('click', '.btn-addnew', function (e) { e.preventDefault(); var form = $(this).closest('form'); var targetId = $(this).data('target'); var target = form.find('#' + targetId); var href = $(this).attr('href'); $.ajax({ url: href, cache: false, type: 'POST', data: form.serialize() }).done(function (html) { target.html(html); }); }); $('form').on('click', '.link-item-remove', function (e) { e.preventDefault(); var form = $(this).closest('form'); var targetId = $(this).data('target'); var target = form.find('#' + targetId); var href = $(this).attr('href'); var formData = form.serialize() + '&index=' + $(this).data('index'); $.ajax({ url: href, cache: false, type: 'POST', data: formData }).done(function (html) { target.html(html); }); }); 

这是因为您回发了模型,并且DefaultModeBinder将模型的值添加到ModelState 。 生成表单控件的HtmlHelper方法(在您的情况下为@Html.EditorFor(m => m.Names, "Names") )使用ModelState的值(如果它们存在(而不是实际的属性值))。 在本答案的第二部分中解释了这种行为的原因。

在您的情况下, ModelState值是

 Name[0]: Foo Name[1]: Bar Name[2]: Baz 

所以即使你返回的更新模型只包含Name[0]: BarName[1]: BazEditorFor()方法,在第一次迭代中将检查EditorFor()Name[0] ,发现它存在并输出Foo

您可以在返回视图之前使用ModelState.Clear()解决此问题(尽管正确的方法是使用PRG模式),但在您的情况下,这似乎都不是必需的,尤其是必须回发整个模型。 您可以简单地回发项目的索引或Name值(或者如果它是复杂对象,然后是ID值),删除该项目并返回指示成功或否则的JsonResult 。 然后在ajax success回调中,从DOM中删除该项。