ModelBinding on model collection
Asked Answered
A

2

6

I am trying to create a very simple form to post back some values from a collection of models

When i click submit, and look at the collection returned it does not contain any objects. I also se that the asp-for does not generate the collection index that i expected.

I have a simple model

public class CustomModel
{
    public int Id { get; set; }
    public string Question { get; set; }
    public string Answer { get; set; }
}

And this is my view

@model ICollection<CustomModel>
<form asp-action="Index" asp-controller="Home" method="POST">
<table>
    @foreach (var m in Model)
    {
        <tr>
            <td><label asp-for="@Model">@m.Question</label><input asp-for="@m.Answer"/></td>
        </tr>
    }
</table>
<input type="submit" value="save" />

Example of how this would look when the page is renderd:

<tr>
            <td><label>Your name?</label><input type="text" id="m_Answer" name="m.Answer" value="" /></td>
        </tr>
        <tr>
            <td><label>Your Age?</label><input type="text" id="m_Answer" name="m.Answer" value="" /></td>
        </tr>

This is were i assumed it would have a index, but instead it looks like it treats each row as an induvidal model instead of a collection of models.

What am i doing wrong here? Is this a bug, or by design?

Github test project https://github.com/lasrol/TestModelBindingList

Assurbanipal answered 27/5, 2016 at 18:31 Comment(0)
N
10

Change your model to @model List<CustomModel> And than use next approach

<form asp-action="Index" asp-controller="Home" method="POST">
<table>
    @for (int i = 0; i < Model.Count; i++)
    {            
        <tr>
            <td>
                <input type="hidden" asp-for="@Model[i].Id" />
                <input type="hidden" asp-for="@Model[i].Question" />            
                <label asp-for="@Model[i].Question">@(Model[i].Question)</label>
                <input asp-for="@Model[i].Answer" />
            </td>
        </tr>            
    }
</table>
<input type="submit" value="save" />

So as you can see you should access to list items via index to properly render name attribute for inputs. Also do not forget to include other item properties via hidden inputs otherwise it values will be loosed in post action.

Ned answered 28/5, 2016 at 7:57 Comment(4)
This works, but is it a bug? Is this a step back, if im not wrong i could use foreach in earlier versions of mvc? Also why can i not use a ICollection, but i can use an IList or List, what is the diffrence?Assurbanipal
Nothing changed from previous version here. You could use foreach for simple types such as List<string> or List<int> but if you want to edit complex types you should use for aproach. You can not use ICollection because this interface does not provide index access. Basically you could use any object which has something like this T this[int index] { get; set; }Ned
What if CustomModel had a field of type List. Could I still use the default ModelBinding for a List of List?Motherwell
Could you try next for (int j = 0; j < Model[i].SubList.Count; j++) { <div><input asp-for="@Model[i].SubList[j].SubListProperty" /></div>} inside main list loop?Ned
E
0

The documentation here suggest there is no need for @ before the variable name. You can also use explicit tag helper annotation by adding th: to the beginning of the tag

@model ICollection<CustomModel>
<form asp-action="Index" asp-controller="Home" method="POST">
  <table>
      @foreach (var m in Model)
      {
          <tr>
              <td><th:label asp-for="m.Answer">@m.Question</label><th:input asp-for="m.Answer"/></td>
          </tr>
      }
  </table>
  <input type="submit" value="save" />
</form>
Extern answered 27/5, 2016 at 18:44 Comment(1)
That method does exactly what the OP doesn't want -- every INPUT has the same name and id as every other INPUT for the same property.Tideway

© 2022 - 2024 — McMap. All rights reserved.