【问题标题】:Posting a Collection and ModelState发布集合和 ModelState
【发布时间】:2012-06-18 19:21:41
【问题描述】:

我的 MVC 应用程序中有一个问题,我不知道如何解决,或者我是否以错误的方式解决它。

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

视图看起来像这样:

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

我的控制器接受这些值:

[HttpPost]
public ActionResult Delete(DeleteItemsModel model)
{
    if( !ModelState.IsValid )
    {
        // ...
    }

    foreach( var id in model.Items.Where(i => i.Delete))
        repo.Delete(id);
}

此方案运行良好。项目正在正确发布,带有 id 和要删除或不删除的标志,并且它们被正确删除。我遇到的问题是我的页面验证失败。我需要再次从数据库中获取项目并将数据发送回视图:

if( !ModelState.IsValid )
{
    var order = repo.GetOrder(id);

    // map
    return View(Mapper.Map<Order, OrderModel>(order));
}

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

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

关于如何解决这种情况或我应该怎么做的任何想法? 有没有人知道如何做

【问题讨论】:

  • @JohnSaunders 我会记住这一点的。
  • 该方法存在问题,您不应该在模型验证失败时从数据库中获取项目。如果你不得不这样做,那么你需要重建你的视图模型。
  • @ElYusubov 如果数据过时怎么办?还是更新一下?
  • 验证失败时没有从数据库中获取项目的问题是我没有将每一条数据都发布到控制器,所以我必须要么开始发布效率低下的数据.

标签: c# asp.net-mvc


【解决方案1】:

有趣的问题。让我们首先用一个简单的例子来说明这个问题,因为从其他答案来看,我不确定每个人都理解这里的问题所在。

假设以下模型:

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

【讨论】:

  • 谢谢!你完全理解我的问题,这是一个很好的解决方案。我认为我已经充分解释了它,但我可能需要更好地措辞我的问题。
【解决方案2】:

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

您可以找到 MVC here 的代码示例的解释(以及许多其他好的 MVC 技巧)。向下滚动到列表中的第 13 项以获得 PRG 说明。

【讨论】:

  • 这很好,但并不能完全解决问题。如果添加了新项目,它将位于列表的顶部。索引 0 处的项目的 ModelState 已检查,现在将检查新项目。
  • 我不确定我是否理解。如果您没有进行任何更改,请重定向并重新显示未更改的表单。如果您确实进行了更改,请重定向并显示修改后的数据。从您的评论看来,您从上次请求检查项目时开始保留一些状态,而不是从您的数据存储或后端新鲜获取信息,这可能是您应该做的。如果这没有帮助,请考虑发布一个带有代码的新问题,以显示您现在正在做什么。
  • 刚刚看到这篇文章 - 我认为它会帮助您解决问题。见jefclaes.be/2012/06/persisting-model-state-when-using-prg.html
  • 保持模型状态是问题所在。请再次阅读问题。我的桌子上有两行。我单击第一行中的第一个复选框。验证失败。我重定向回 GET 并在我的 TempData 中保留模型状态。数据库检索数据,现在有 3 个项目而不是 2 个......并且最近添加到数据库的项目现在位于第一行。表格中的第一项现在有一个复选框,而不是正确的。
  • 问题是这样的:ModelState 会有一个条目基本上是这样的:Items[0].Delete=true。 0 是索引。如果页面重新加载并且 DIFFERENT 项目现在位于索引 0 处,那么 ModelState 现在基本上是错误的,因为它引用的项目与第一次发布的项目不同。
猜你喜欢
  • 2015-08-25
  • 2015-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-25
  • 2017-01-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多