Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
276 views
in Technique[技术] by (71.8m points)

c# - Strange behaviour in ASP.NET MVC: removing item from a list in a nested structure always removes the last item

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

}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I believe that problem lies with ModelState. When the view gets rendered, which I assume is where the issue lies, after the POST, the last value is not displayed i.e. removed from the view.

The issue is that Model.Children.Count will return the correct number of elements to display.

Lets break this down...

So if you have initially had 5 then removed the first one which is at index 0 based on the Guid, you now have items 4 items left with indexes 1 to 4.

However, when rendering the view after the post, the HtmlHelpers do not look at the values in model posted, but rather the values contained within the ModelState. So in the ModelState, item with index 0 still exists and since the loop is now looping to 4, the last element will not be displayed.

The solution, use ModelState.Clear()


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...