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

3
我已经编写了一些代码,以使用AJAX在ASP.NET MVC中动态添加和删除集合中的元素。添加新项目到集合中正常工作,但是删除却没有。模型集合按预期更新(通过索引删除适当的项),但呈现的HTML始终显示已删除了最后一项(而不是指定索引处的项)。
例如,假设我有以下项目:
  • Foo
  • Bar
  • Baz
当我点击“Foo”旁边的“remove”时,我期望生成的呈现的HTML应如下所示:
  • Bar
  • Baz
当我通过控制器操作进行调试时,情况似乎是这样的,因为模型上的Names集合只包含这些项。然而,返回给我的AJAX处理程序的呈现的HTML是:
  • Foo
  • Bar
我认为问题可能与缓存有关,但是我尝试过的所有内容(OutputCache指令,在$.ajax中设置cache:false等)都无效。
这里是代码:
namespace MvcPlayground.Models
{
   using System;
   using System.Collections.Generic;
   using System.Linq;
   using System.Text;
   using System.Threading.Tasks;

   public class DemoViewModel
   {
      public List<string> Names { get; set; }

      public DemoViewModel()
      {
         Names = new List<string>();
      }
   }
}

DemoController.cs

明显的问题在于RemoveName方法。我可以验证PartialViewResult的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<string>

@for (int i = 0; i < Model.Count; i++)
{
   <p>
      @Html.EditorFor(m => m[i]) @Html.ActionLink("remove", "RemoveName", null, new { data_target = "names", data_index = i, @class = "link link-item-remove" })
   </p>
}

Index.cshtml

这是加载的初始页面,没有太复杂的内容。

@model MvcPlayground.Models.DemoViewModel

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>


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

    <div class="form-horizontal">
        <h4>Demo</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

       <div class="container-collection" id="names">
         @Html.EditorFor(m => m.Names, "Names")
       </div>
        @Html.ActionLink("Add New", "AddName", "Demo", null, new { data_target = "names", @class = "btn btn-addnew" })

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

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);
   });

});
1个回答

4
这是因为您回传了您的模型,您的模型的值被DefaultModelBinder添加到ModelState中。生成表单控件的HtmlHelper方法(在您的情况下是@Html.EditorFor(m => m.Names, "Names")) 如果存在ModelState的值(而不是实际属性值),则使用这些值。这种行为的原因在此答案的第二部分有解释。
在您的情况下,ModelState的值为:
Name[0]: Foo
Name[1]: Bar
Name[2]: Baz

因此,即使您返回的更新模型只包含Name[0]: BarName[1]: BazEditorFor()方法在第一次迭代中仍会检查ModelStateName[0],发现它存在并输出Foo
您可以通过在返回视图之前使用ModelState.Clear()来解决此问题(尽管正确的方法是使用PRG模式),但在您的情况下似乎没有必要这样做,尤其是要回传整个模型。您只需回传该项的索引或Name值(如果它是一个复杂对象,则为ID值),删除该项并返回JsonResult指示成功与否。然后在ajax的success回调中,从DOM中删除该项即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接