在ASP.NET MVC奇怪的现象:从列表中嵌套结构去除项目始终删除最后一个项目项目、嵌套、奇怪、现象

2023-09-03 05:34:16 作者:欲劫无渡

场景

我有一个父/子模型(准确地说小问卷形式和联系人的一个或多个号码)。由于历史原因,所有的这一切都已经做了相同的形式让用户必须为父母和一个孩子的一种形式,他们会打一个按钮来添加更多的孩子。儿童有几个标准字段和同父,没有任何幻想。主要要求是,数据不能接触数据库直到所有的有效和安装,而我必须要回去服务器的添加删除的孩子。

执行

这是非常快的得到这个工作的ASP.NET MVC(使用 MVC 2 VS 2010 )。我有两个型号,一个是父母,一个是孩子,只拿到一个控制器。控制器有一个创建方法,这是一个 GET ,并得到含有一个孩子一个新的品牌新的父默认视图。我使用编辑模板作为其作品很好子模型。

我有有一个HTML表单中的保存和添加子,我已经删除按钮,每个窗体。因为这不能被存储在数据库中,我存储在形式本身的温度模型和不言而喻来回浏览器和服务器之间。 Perfromance是这里不多的问题,但开发成本,因为有相当多的这些形式 - 所以请不要分心太多被暗示,虽然我AP preciate意见的另一种方法呢

为了找出要删除的孩子,我创建临时GUID标识,并将其与孩子相关联。这将走上了删除按钮的HTML输入的值(当你有多个动作和相同的形式惯用的伎俩)。

我有禁用缓存。

问题

请看看下面的代码片段。我已经调试了code和我所看到的总是正确的GUID传递,从列表中的控制器和正确的项目中删除正确的项目模板中被渲染。但总是最后一个被删除!我通常单击第一个删除,可以看到,最后被删除。我继承和第一个项目是删除的最后幸福。

控制器

 公众的ActionResult创建()
    {
        EntryForm1 entryForm1 =新EntryForm1();
        entryForm1.Children.Add(新的儿童(充满我,FILL ME){TempId = Guid.NewGuid()});
        返回视图(EntryForm1View,entryForm1);
    }

    [HttpPost]
    公众的ActionResult创建(EntryForm1 Form1中,收集的FormCollection,字符串添加)
    {
        如果(添加==加)
            form1.Children.Add(新的儿童(充满我,FILL ME){TempId = Guid.NewGuid()});

        变种删除= collection.AllKeys.Where(S => s.StartsWith(DELETE_));
        collection.Clear();
        如果(deletes.Count()大于0)
        {
            字符串删除= deletes.FirstOrDefault();
            删除= delete.Replace(DELETE_,);
            GUID G = Guid.Parse(删除);
            VAR儿童= form1.Children.Where(X => x.TempId == G).ToArray();
            的foreach(孩子的孩子在儿童)
            {
                form1.Children.Remove(子);
            }
            //这里正确的项目被删除,请相信我!
        }
        如果(ModelState.IsValid)
        {
            返回重定向(/);
        }
        返回视图(EntryForm1View,窗口1);
    }
 
ASP.NET MVC学习笔记一

查看片段

 <%的for(int i = 0; I< Model.Children.Count;我++)
  {%>
        < H4> <%:Html.EditorFor(M => m.Children [I])%GT;< / H4>

        <%
  }%GT;
        &其中p为H.;
            <输入类型=提交值=创建NAME =添加/>
            <输入类型=提交值=加NAME =添加/>
        &所述; / P>
 

儿童编辑模板段

 <%:Html.HiddenFor(X => x.TempId)%>
        < / SPAN>
        <输入类型=提交名称='DELETE_<%:Html.DisplayTextFor(M => m.TempId)%>值=删除/>
 

非常感谢您的时间和精力

更新

有人问我的模型类和我分享他们的,正是因为他们的。 Entryform1 是父和 Somesing 是孩子。

公共类Somesing     {

 公共Somesing()
    {

    }

    公共Somesing(字符串0,字符串):这个()
    {
        OneSing = O;
        AnozerSing =一个;
    }

    [StringLength(2)]
    公共字符串OneSing {获得;组; }

    [StringLength(2)]
    公共字符串AnozerSing {获得;组; }

    公众的Guid TempId {获得;组; }

}

公共类EntryForm1
{

    公共EntryForm1()
    {
        辛斯=新的名单,其中,Somesing>();
    }

    公共字符串名字{获得;组; }

    公共字符串名字{获得;组; }

    公众诠释年龄{获得;组; }

    公开名单< Somesing>辛斯{获得;组; }

}
 

解决方案

确定,如艾哈迈德指出,的ModelState 是关键问题。它包含集合作为这样的:

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

现在,如果我从列表中删除项0,现在的项目将在列表中向上移动,并在ModelState中的数据会不同步的模式。我曾预计ASP.NET MVC是足够聪明的发现和重新排序,但也就是要求太高。

我实际执行PRG(后重定向的获得),并通过保持模型的会议上,我能够显示正确的信息,但再一次,这将删除所有验证集合中,如果模型本身是有效的,它会高兴地保存和重定向回家乡/".很显然,这是不能接受的。

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

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

Scenario

I have a parent/child model (to be exact a small questionnaire form and a one or more number of contacts). For historic reasons, all of this would have been done on the same form so user would have a form for the parent and one child and they would hit a button to add more children. Child has a few standard fields and the same with the parent, nothing fancy. Main requirement is that the data must not touch the database until all is valid and setup while I would have to go back to server for adding deleting children.

Implementation

It was very quick to get this working in ASP.NET MVC (using MVC 2 with VS 2010). I got two models, one for parent and one for the child and got only one controller. Controller has a Create Method which is a get and gets a default view with a fresh brand new parent containing one child. I use editor template for the child model which works nicely.

I have one HTML form which has a "save" and "add child" and I have "delete" button for each form. Since this cannot be stored in database, I store the temp model in the form itself and it goes back and forth between browser and server. Perfromance is not much of an issue here but the cost of development since there are quite a few of these forms - so please do not get distracted too much by suggesting an alternative approach although I appreciate comments anyway.

In order to find out which child to delete, I create temp GUID Ids and associate them with the child. This will go onto the HTML input's value for delete button (usual trick when you have multiple actions and the same form).

I have disabled caching.

Issue

Please have a look at the snippets below. I have debugged the code and I have seen always correct GUID being passed, correct item removed from the list in the controller and correct items being rendered in the template. BUT ALWAYS THE LAST ONE GETS DELETED!! I usually click the first delete and can see that the last gets deleted. I carry on and first item is the last being deleted.

Controller

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

View snippet

    <% 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>

Child Editor template snippet

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

Many thanks for your time and attention

UPDATE

I was asked for model classes and I am sharing them as exactly as they are. Entryform1 is the parent and Somesing is the child.

public class 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; }

}

解决方案

OK, as Ahmad pointed out, ModelState is the key to the issue. It contains the collection as such:

FirstName LastName ... Sings[0].OneSing Sings[0].AnozerSing Sings[1].OneSing Sings[1].AnozerSing Sings[2].OneSing Sings[2].AnozerSing

Now if I delete item 0 from the list, now the items will move up in the list and the data in the ModelState will go out of sync with the model. I had expected ASP.NET MVC to be clever enough to find out and re-order, but well that is asking for too much.

I actually implemented PRG (post-redirect-get) and by keeping the model in session, I was able to display correct information but again, this will remove all the validation in the collection and if model itself is valid, it will happily save and redirect back to home "/". Clearly this is not acceptable.

So one solution is to remove all items in the ModelState and then add a new entry for the model itself (with key of EmptyString). This can actually work alright if you populate it with error "Item deleted" as this will be displayed in the validation summary.

Another solution is to manually change the items in the model state and re-arrange them based on the new indexes. This is not easy but possible.