渲染的局部视图与模型不匹配
所以我编写了一些代码,允许使用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]: Bar
和Name[1]: Baz
, EditorFor()
方法,在第一次迭代中将检查EditorFor()
值Name[0]
,发现它存在并输出Foo
。
您可以在返回视图之前使用ModelState.Clear()
解决此问题(尽管正确的方法是使用PRG模式),但在您的情况下,这似乎都不是必需的,尤其是必须回发整个模型。 您可以简单地回发项目的索引或Name
值(或者如果它是复杂对象,然后是ID值),删除该项目并返回指示成功或否则的JsonResult
。 然后在ajax success
回调中,从DOM中删除该项。