发布Collection和ModelState

我在我的MVC应用程序中遇到问题,我不确定如何解决或者我是否以错误的方式处理它。

我有一个控制器/视图,显示一个带有复选框的网格中的项目列表,当项目发布到我的控制器时,我想根据传入的id从我的数据库中删除行。

视图看起来像这样:

@for(int index = 0; index < Model.Items.Length; index++) {  @Html.HiddenFor(m => m[index].Id) @Html.CheckBoxFor(m => m[index].Delete)  } 

我的控制器接受以下值:

 [HttpPost] public ActionResult Delete(DeleteItemsModel model) { if( !ModelState.IsValid ) { // ... } foreach( var id in model.Items.Where(i => i.Delete)) repo.Delete(id); } 

这种情况很好。 项目正确发布带有id和要删除的标志,并且它们被正确删除。 我遇到的问题是我的页面validation失败。 我需要再次从数据库中获取项目并将数据发送回视图:

 if( !ModelState.IsValid ) { var order = repo.GetOrder(id); // map return View(Mapper.Map(order)); } 

在用户获取要删除的项目列表和单击“提交”之间的时间内,可能已添加新项目。 现在,当我拉取数据并将其发送回视图时,列表中可能会有新项目。

问题示例:
我在我的页面上执行HTTP GET,我的网格中有两个项目,Id为2和1.我选择第一行(Id 2,按最近排序),然后单击Submit。 页面上的validation失败,我将视图返回给用户。 网格中现在有三行(3,2,1)。 MVC将在FIRST项目上设置复选框(现在ID为3)。 如果用户没有检查这些数据,那么他们可能会删除错误的内容。

有关如何修复此方案或我应该做什么的任何想法? 有没有人知道如何

有趣的问题。 让我们首先通过一个简单的例子说明问题,因为从其他答案来看,我不确定每个人都明白这里的问题是什么。

假设以下模型:

 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++) { 
@Html.HiddenFor(m => m[i].Id) @Html.CheckBoxFor(m => m[i].Delete) @Model[i].Id
} }

这是将要发生的事情:

用户导航到“ Index操作,选择要删除的第一个项目并单击“删除”按钮。 以下是视图在提交表单之前的样子:

在此处输入图像描述

调用Delete操作,当再次呈现视图时(因为存在一些validation错误),将向用户显示以下内容:

在此处输入图像描述

看看如何预选错误的项目?

为什么会这样? 因为HTML帮助程序在绑定时使用ModelState值而不是模型值,这是设计使然。

那么如何解决这个问题呢? 阅读Phil Haack的以下博客文章: http : //haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

在他的博客文章中,他谈到了非顺序指数并给出了以下示例:

 

看看我们如何不再使用增量索引输入按钮的名称?

我们如何将此应用于我们的示例?

像这样:

 @model MyViewModel[] @Html.ValidationSummary() @using (Html.BeginForm()) { @Html.HttpMethodOverride(HttpVerbs.Delete) for (int i = 0; i < Model.Length; i++) { 
@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
} }

现在问题得到解决。 或者是吗? 你见过这个观点现在代表的可怕混乱吗? 我们已经解决了一个问题,但我们在视图中引入了一些绝对可恶的东西。 我不了解你,但当我看到这个我想呕吐。

那可以做些什么呢? 我们应该阅读Steven Sanderson的博客文章: http : //blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/其中他提出了一个非常有趣的习俗Html.BeginCollectionItem帮助器,使用如下:

 
<% using(Html.BeginCollectionItem("gifts")) { %> Item: <%= Html.TextBoxFor(x => x.Name) %> Value: $<%= Html.TextBoxFor(x => x.Price, new { size = 4 }) %> <% } %>

注意表单元素如何包装在这个帮助器中?

这个助手做了什么? 它取代Guids强类型助手生成的顺序索引,并使用额外的隐藏字段在每次迭代时设置此索引。


话虽如此,只有在需要从Delete操作中的数据库中获取新数据时才会出现问题。 如果您依赖模型绑定器进行补水,则根本不存在任何问题(除非存在模型错误,否则您将显示具有旧数据的视图 – >这可能不会出现问题):

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

此问题的常见解决方案是使用Post-Redirect-Get模式 。

你可以在这里找到MVC代码示例的解释(以及一堆其他好的MVC技巧)。 向下滚动到列表中的项目13以获取PRG说明。