A vista parcial renderizada não corresponde ao modelo
Então, eu escrevi um código para permitir adicionar e remover elementos de uma coleção dinamicamente no ASP.NET MVC usando AJAX. Adicionar novos itens à coleção funciona conforme o esperado, mas a remoção não. A coleção de modelos é atualizada conforme o esperado (o item apropriado é removido pelo índice), mas o HTML renderizado mostra consistentemente que o último item foi removido (em vez do item no índice especificado).
Por exemplo, digamos que tenho os seguintes itens:
FooBarraBazQuando clico em "remover" ao lado do item chamado "Foo", espero que o HTML renderizado resultante tenha a seguinte aparência:
BarraBazQuando depuro através da ação do controlador, esse parece ser o caso, pois a coleção Names no modelo contém apenas esses itens. No entanto, o HTML renderizado retornado ao meu manipulador AJAX é:
FooBarraEu pensei que o problema tivesse a ver com cache, mas nada que eu tentei (diretiva OutputCache, definindo cache: false em $ .ajax, etc) está funcionando.
Aqui está o código:
DemoViewModel.csnamespace 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.csO problema aparente aqui está no método RemoveName. Posso verificar que a propriedade Model do PartialViewResult reflete o estado da coleção como eu esperava, mas, uma vez renderizado para o cliente, o HTML NÃO é o que eu esperava.
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.cshtmlEste é o modelo de editor que estou usando para exibir a lista de nomes. Funciona como esperado ao adicionar um novo item à coleção.
@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.cshtmlEsta é a página inicial carregada, nada muito complicado aqui.
@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.jsEste script lida com as chamadas para AddName e RemoveName. Tudo aqui funciona como eu esperava.
$('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);
});
});