【问题标题】:Rendered partial view does not match model渲染的局部视图与模型不匹配
【发布时间】:2017-07-18 20:20:16
【问题描述】:

所以我编写了一些代码来允许在 ASP.NET MVC 中使用 AJAX 动态添加和删除集合中的元素。将新项目添加到集合中按预期工作,但删除不会。模型集合按预期更新(通过索引删除了相应的项),但呈现的 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<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);
   });

});

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-5


    【解决方案1】:

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

    在您的情况下,ModelState 值是

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

    因此,即使您返回的更新模型仅包含 Name[0]: BarName[1]: BazEditorFor() 方法在第一次迭代中将检查 Name[0]ModelState 值,发现它存在并输出Foo.

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-23
      • 1970-01-01
      • 2018-01-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多