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()
returnsi = 1
, so I know that part works too. - When I inspect the
persons
object after theRemoveAt(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, whenEditorFor()
gets called, the firstPerson
object that gets sent into the template is Bob Smith. The secondPerson
object is Anne Walker. - Here's the crucial point that I don't understand: in PersonTemplate.cshtml,
this.Model
hasFirstName = "Anne"
andLastName = "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?