1

I'm trying to set up a view where the user can add to and delete from a list of people. When I'm debugging, after deleting an item with RemoveAt(i), the list gets updated correctly, and when I inspect the Model while the View is being created, the removed item is not there. However, after I call EditorFor(x => name) in the for loop that displays each item in the list, the correct number of items is rendered, but the values are not the same as what the Model contained.

I'm using the techniques in this answer to display each item.

Relevant code:

Controller:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Persons(IList<Person> persons, string command)
{
    if (!ModelState.IsValid && !command.StartsWith("Delete"))
        return View(persons);
    if (command == "AddNew")
    {
        List<Person> newPersons = persons?.ToList() ?? new List<Person>();
        newPersons.Add(new Person());
        return View(newPersons);
    }
    if (command.StartsWith("Delete"))
    {
        if (int.TryParse(command.Substring(6), out int i))
            persons.RemoveAt(i);
        else
            ModelState.AddModelError("", "There was an error deleting the person.");
        return View(persons);
    }
    return RedirectToAction(command == "Back" ? "Index" : "Summary");
}

Person.cs (model)

private string firstName;
private string middleName;
private string lastName;

[Required(AllowEmptyStrings = false)]
[Display(Name = "First Name")]
public string FirstName
{
    get => firstName;
    set => firstName = value?.Trim();
}

[Display(Name = "Middle Name")]
public string MiddleName
{
    get => middleName;
    set => middleName = value?.Trim();
}

[Required(AllowEmptyStrings = false)]
[Display(Name = "Last Name")]
public string LastName
{
    get => lastName;
    set => lastName = value?.Trim();
}

Persons.cshtml:

@model IEnumerable<Person>
<div class="text-center">
    <button type="submit" name="command" value="AddNew" class="btn btn-primary btn-lg">
        Add another person
    </button>
</div>
{
    int i = 0;
    foreach (Person person in Model)
    {
        @Html.EditorFor(x => person, "PersonTemplate", $"Persons[{i++}]")
    }
}
<script type="text/javascript>
$(document).on("click", "button[value='Delete']", function() {
    const r = $(this).parent().prev().find("select").attr("name");
    const s = r.substring(8, r.indexOf("]"));
    $(this).val(`Delete${s}`);
})
</script>

PersonTemplate.cshtml:

<div class="row px-3 pb-3">
    <div class="col-lg-3 col-md-6">
        @Html.LabelFor(x => x.FirstName, new { @class = "required" })
        @Html.EditorFor(x => x.FirstName, new { htmlattributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(x => x.FirstName)
    </div>
    <div class="col-lg-3 col-md-6">
        @Html.LabelFor(x => x.MiddleName)
        @Html.EditorFor(x => x.MiddleName, new { htmlattributes = new { @class = "form-control" } })
    </div>
    <div class="col-lg-4 col-md-7">
        @Html.LabelFor(x => x.LastName, new { @class = "required" })
        @Html.EditorFor(x => x.LastName, new { htmlattributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(x => x.LastName)
    </div>
</div>
<button type="submit" name="command" value="Delete" class="btn btn-outline-danger">
    Delete
</button>

Adding a new blank item to the list works fine. Deleting an item seems to work, all the way up until the EditorFor command. Here's an example of a scenario I go through:

  • the screen shows 3 names: Bob Smith, Larry Jones, Anne Walker.
  • I click the "Delete" button next to Larry Jones
  • As I step through the controller action, command = "Delete1", so I know the javascript that identifies which one to delete works.
  • The int.TryParse() returns i = 1, so I know that part works too.
  • When I inspect the persons object after the RemoveAt(i) line, there are two Person objects in the list: Bob Smith and Anne Walker.
  • When I step into the View, and inspect the Model object, there are two Person objects: Bob Smith and Anne Walker.
  • In the for loop, when EditorFor() gets called, the first Person object that gets sent into the template is Bob Smith. The second Person object is Anne Walker.
  • Here's the crucial point that I don't understand: in PersonTemplate.cshtml, this.Model has FirstName = "Anne" and LastName = "Walker", but the MvcHtmlString that gets generated by @Html.EditorFor(x => x.FirstName) returns the following:
<input class="text-box single-line" id="Persons_1__FirstName" name="Persons[1].FirstName" type="text" value="Larry" />
  • @Html.EditorFor(x => x.LastName) returns:
<input class="text-box single-line" id="Persons_1__LastName" name="Persons[1].LastName" type="text" value="Jones" />

What happened to Anne Walker? In PersonTemplate, this.Model still shows FirstName = "Anne" and LastName = "Walker", but the input values use Larry Jones instead.

This happens no matter which one I delete in the list. If I have 5 people in the list, and I delete person #1, then the view displays persons 1, 2, 3, and 4. If I delete Person 4, it still displays persons 1, 2, 3, and 4. If I delete Person 5, it displays persons 1, 2, 3, and 4. Why doesn't it render the correct value from the model?

John
  • 11
  • 4
  • When you delete an item from an array all the items above the deleted item will move one index down. So 1) A 2) B 3) C 4) D 5) E. When you remove item 3 you get 1) A 2) B 3) D 4) E. – jdweng Oct 09 '19 at 17:05
  • @jdweng Right, but even if I cast the list (after removing an item) to a new list and pass that into the View, the original items still display. The EditorFor doesn't match what the Model contains. – John Oct 09 '19 at 17:09
  • Then the model when data was created and model when cast are not the same. – jdweng Oct 10 '19 at 01:59
  • @jdweng When I'm debugging and viewing the Model in the View, it does not contain the name that was removed in the controller. Yet it still generates the name that was removed. – John Oct 10 '19 at 17:03
  • I figured it out -- I had to [clear the ModelState](https://stackoverflow.com/a/9144621/2665182), after that it worked. – John Oct 18 '19 at 17:13

0 Answers0