ASP.NET MVC中的奇怪行为:从嵌套结构中的列表中删除项目始终会删除最后一项

时间:2010-12-14 11:59:13

标签: c# .net asp.net-mvc asp.net-mvc-2

方案

我有一个父/子模型(确切地说是一个小问卷表格和一个或多个联系人)。由于历史原因,所有这些都是在同一个表单上完成的,因此用户可以拥有父表单和一个孩子的表单,他们会点击一个按钮来添加更多的孩子。孩子有一些标准的领域和父母一样,没什么特别的。主要要求是数据必须不会触及数据库,直到所有数据都有效并进行设置,而我必须返回服务器才能添加删除子项。

实施

在ASP.NET MVC中使用 MVC 2和VS 2010 非常快速。我有两个模型,一个用于父母,一个用于孩子,只有一个控制器。控制器有一个Create方法get,并获得一个默认视图,其中包含一个包含一个孩子的全新父级。我使用编辑器模板作为可以很好地运行的子模型。

我有一个HTML表单,其中包含“save”和“add child”,每个表单都有“删除”按钮。由于这不能存储在数据库中,我将临时模型存储在表单本身中,它在浏览器和服务器之间来回传递。 Perfromance在这里不是一个问题,但是开发成本因为这些形式中有很多 - 所以请不要因为建议采用其他方法而分散注意力,尽管我还是赞赏评论。

为了找出要删除的子项,我创建了临时GUID ID并将它们与子项关联起来。这将转到HTML输入的删除按钮值(当你有多个动作和相同的表格时通常的技巧)。

我禁用了缓存。

问题

请查看下面的代码段。我已经调试了代码,我看到总是正确的GUID被传递,正确的项目从控制器的列表中删除,并更正在模板中呈现的项目。但总是最后一个删除!我经常点击第一次删除,可以看到最后一次删除。我继续,第一项是最后一项被删除。

控制器

    public ActionResult Create()
    {
        EntryForm1 entryForm1 = new EntryForm1();
        entryForm1.Children.Add(new Child("FILL ME", "FILL ME"){ TempId = Guid.NewGuid()});
        return View("EntryForm1View", entryForm1);
    }

    [HttpPost]
    public ActionResult Create(EntryForm1 form1, FormCollection collection, string add)
    {
        if (add == "add")
            form1.Children.Add(new Child("FILL ME", "FILL ME") {TempId = Guid.NewGuid()});

        var deletes = collection.AllKeys.Where(s => s.StartsWith("delete_"));
        collection.Clear();
        if (deletes.Count() > 0)
        {
            string delete = deletes.FirstOrDefault();
            delete = delete.Replace("delete_", "");
            Guid g = Guid.Parse(delete);
            var Children = form1.Children.Where(x => x.TempId == g).ToArray();
            foreach (Child child in Children)
            {
                form1.Children.Remove(child);
            }               
            // HERE CORRECT ITEM IS DELETED, BELIEVE ME!!
        }
        if (ModelState.IsValid)
        {
            return Redirect("/");
        }
        return View("EntryForm1View", form1);
    }

查看摘要

    <% for (int i = 0; i < Model.Children.Count;i++ )
  {%>
        <h4> <%: Html.EditorFor(m=>m.Children[i])%></h4>

        <%
  }%>
        <p>
            <input type="submit" value="Create" name="add" />
            <input type="submit" value="add" name="add" />
        </p>

子编辑器模板代码

        <%: Html.HiddenFor(x=>x.TempId) %>
        </span>
        <input type="submit" name='delete_<%: Html.DisplayTextFor(m => m.TempId) %>' value="Delete" />

非常感谢您的时间和关注


更新

我被要求进行模型课程,我正在分享它们。 Entryform1父级Somesing子级

公共课Somesing     {

    public Somesing()
    {

    }

    public Somesing(string o, string a) : this()
    {
        OneSing = o;
        AnozerSing = a;
    }

    [StringLength(2)]
    public string OneSing { get; set; }

    [StringLength(2)]
    public string AnozerSing { get; set; }

    public Guid TempId { get; set; }

}

public class EntryForm1
{

    public EntryForm1()
    {
        Sings = new List<Somesing>();
    }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }

    public List<Somesing> Sings { get; set; }

}

3 个答案:

答案 0 :(得分:7)

我认为问题在于ModelState。当视图被渲染时,我认为是问题所在,在POST之后,最后一个值不显示,即从视图中删除。

问题是Model.Children.Count将返回要显示的正确数量的元素。

让我们打破这个......

所以如果你最初有5然后根据Guid删除了第一个在索引0的那个,你现在有4个项目,其中索引为1到4。

但是,在帖子之后渲染视图时,HtmlHelpers不会查看已发布模型中的值,而是查看ModelState中包含的值。因此,在ModelState中,索引为0的项仍然存在,并且由于循环现在循环为4,因此不会显示最后一个元素。

解决方案,使用ModelState.Clear()

答案 1 :(得分:2)

ModelState.Clear() 将解决此问题。

ModelState.Clear()用于清除错误,但它也用于强制MVC引擎重建要传递给View的模型。

答案 2 :(得分:1)

好的,正如艾哈迈德指出的那样,ModelState是问题的关键。它包含这样的集合:

  • 名字
  • ...
  • 辛斯[0] .OneSing
  • 辛斯[0] .AnozerSing
  • 辛斯[1] .OneSing
  • 辛斯[1] .AnozerSing
  • 唱[2] .OneSing
  • 唱[2] .AnozerSing

现在,如果我从列表中删除项目0,现在项目将在列表中向上移动,并且ModelState中的数据将与模型不同步。我原本以为ASP.NET MVC足够聪明,能够找到并重新排序,但这要求太多了。

我实际上实现了PRG(post-redirect-get)并且通过将模型保持在会话中,我能够显示正确的信息但是,这将删除集合中的所有验证以及模型本身是否有效,它会愉快地保存并重定向回到主页“/”。显然这是不可接受的。

所以一个解决方案是删除ModelState中的所有项目,然后为模型本身添加一个新条目(密钥为EmptyString)。如果您使用错误“项目已删除”填充它,这实际上可以正常工作,因为它将显示在验证摘要中。

另一个解决方案是手动更改模型状态中的项目,并根据新索引重新排列它们。这不容易,但可能。