有趣的问题。让我们首先用一个简单的例子来说明问题,因为从其他答案来看,我不确定每个人都明白这里的问题是什么。
假设有以下模型:
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
操作,选择要删除的第一个项目,然后单击“删除”按钮。以下是他提交表单之前的视图:
调用删除操作,并且当再次呈现视图时(因为存在一些验证错误),用户会看到以下内容:
看看如何预选错误的项目?
为什么会发生这种情况?因为 HTML 帮助程序在绑定时优先使用 ModelState 值而不是模型值,这是设计使然。
那么如何解决这个问题呢?阅读 Phil Haack 的以下博客文章:http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx 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/ 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>
注意表单元素是如何包装在这个助手中的吗?
这个助手是做什么的?它用 Guid 替换了强类型助手生成的顺序索引,并使用额外的隐藏字段在每次迭代时设置此索引。
话虽如此,仅当您需要在删除操作中从数据库获取新数据时,才会出现问题。如果您依靠模型绑定器进行再水合,则根本不会有任何问题(除非存在模型错误,您将显示带有旧数据的视图 - >这可能根本不是那么有问题):
[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");
}