有趣的问题。让我们首先用一个简单的例子来说明这个问题,因为从其他答案来看,我不确定每个人都理解这里的问题所在。
假设以下模型:
public class MyViewModel
{
public int Id { get; set; }
public bool Delete { get; set; }
}
以下控制器:
public class HomeController : Controller
{
public ActionResult Index()
{
// Initially we have 2 items in the database
var model = new[]
{
new MyViewModel { Id = 2 },
new MyViewModel { Id = 1 }
};
return View(model);
}
[HttpDelete]
public ActionResult Index(MyViewModel[] model)
{
// simulate a validation error
ModelState.AddModelError("", "some error occured");
if (!ModelState.IsValid)
{
// We refetch the items from the database except that
// a new item was added in the beginning by some other user
// in between
var newModel = new[]
{
new MyViewModel { Id = 3 },
new MyViewModel { Id = 2 },
new MyViewModel { Id = 1 }
};
return View(newModel);
}
// TODO: here we do the actual delete
return RedirectToAction("Index");
}
}
和一个观点:
@model MyViewModel[]
@Html.ValidationSummary()
@using (Html.BeginForm())
{
@Html.HttpMethodOverride(HttpVerbs.Delete)
for (int i = 0; i < Model.Length; i++)
{
<div>
@Html.HiddenFor(m => m[i].Id)
@Html.CheckBoxFor(m => m[i].Delete)
@Model[i].Id
</div>
}
<button type="submit">Delete</button>
}
以下是将会发生的事情:
用户导航到该Index
操作,选择要删除的第一个项目并单击“删除”按钮。这是他提交表单之前的视图:
调用 Delete 操作,当再次呈现视图时(因为存在一些验证错误),用户将看到以下内容:
看看如何预选了错误的项目?
为什么会这样?因为 HTML 助手在绑定时优先使用 ModelState 值而不是模型值,这是设计使然。
那么如何解决这个问题呢?通过阅读 Phil Haack 的以下博客文章:http: //haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
在他的博客文章中,他谈到了非序列索引并给出了以下示例:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
看看我们如何不再为输入按钮的名称使用增量索引?
我们如何将其应用到我们的示例中?
像这样:
@model MyViewModel[]
@Html.ValidationSummary()
@using (Html.BeginForm())
{
@Html.HttpMethodOverride(HttpVerbs.Delete)
for (int i = 0; i < Model.Length; i++)
{
<div>
@Html.Hidden("index", Model[i].Id)
@Html.Hidden("[" + Model[i].Id + "].Id", Model[i].Id)
@Html.CheckBox("[" + Model[i].Id + "].Delete", Model[i].Delete)
@Model[i].Id
</div>
}
<button type="submit">Delete</button>
}
现在问题已解决。或者是吗?您是否看到该视图现在所代表的可怕混乱?我们已经解决了一个问题,但我们在视图中引入了一些绝对可恶的东西。我不了解你,但当我看到这个我想呕吐。
那么可以做些什么呢?我们应该阅读 Steven Sanderson 的博客文章:http: //blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/其中他提出了一个非常有趣的自定义Html.BeginCollectionItem
像这样使用的助手:
<div class="editorRow">
<% using(Html.BeginCollectionItem("gifts")) { %>
Item: <%= Html.TextBoxFor(x => x.Name) %>
Value: $<%= Html.TextBoxFor(x => x.Price, new { size = 4 }) %>
<% } %>
</div>
注意表单元素是如何包装在这个助手中的?
这个助手是做什么的?它用 Guids 替换了强类型助手生成的顺序索引,并使用一个额外的隐藏字段在每次迭代时设置此索引。
话虽如此,仅当您需要在删除操作中从数据库中获取新数据时才会出现问题。如果您依靠模型粘合剂来补充水分,则根本不会有任何问题(除非存在模型错误,您将使用旧数据显示视图->这可能不是那么有问题):
[HttpDelete]
public ActionResult Index(MyViewModel[] model)
{
// simulate a validation error
ModelState.AddModelError("", "some error occured");
if (!ModelState.IsValid)
{
return View(model);
}
// TODO: here we do the actual delete
return RedirectToAction("Index");
}