0

I have a model with a property of type List. I use this property to show values on my view. I use a foreach loop to go through each item in the list and displaying them using DisplayFor and TextBoxFor. That part is working fine; I've used TextBoxFor before and any user entered text gets past to the model on submit. However, when using a list, the list is null when the form is submitted. All the other properties in the model are updated and I can access them correctly. What is preventing the list from binding and how can I fix this?

ModelInstrumentListingDetail

public class ModelInstrumentListingDetail
{
    public int InstrumentTagID { get; set; }
    public string InstrumentTagDescription { get; set; }

    public List<ModelInstrumentListingItem> items { get; set; }
}

ModelInstrumentListingItem

public class ModelInstrumentListingItem
{
    public string Field { get; set; }
    public string Label { get; set; }
}

View

@model GPC.Models.ModelInstrumentListingDetail

@using (Html.BeginForm("InstrumentListingAdd", "Home", FormMethod.Post, new { id = "InstrumentListingDetailForm" }))
{
  <table>
   <tr>
    <td>
         @Html.TextBoxFor(m => m.InstrumentTagDescription)
    </td>
   </tr>

  @foreach (GPC.Models.ModelInstrumentListingItem item in Model.items)
  {
   <tr>
      <td>
          @Html.DisplayFor(m => item.Label)
      </td>
      <td>
          @Html.TextBoxFor(m => item.Field)
      </td>
   </tr>
   }
</table>
<input id="submitInstrumentListingAdd" name="submitInstrumentListingAdd" value="Add" type="submit" class="SaveButton" />
}

Controller

[HttpPost]
public ActionResult InstrumentListingAdd(ModelInstrumentListingDetail model, string submitInstrumentListingAdd)
{
  ...
}

On submit, the model posted to the controller has InstrumentDescription with a value, but items is null. I need to figure out how to fill items with the new values.

boilers222
  • 1,901
  • 7
  • 33
  • 71
  • possible duplicate of [ASP.NET MVC 4 - for loop posts model collection properties but foreach does not](http://stackoverflow.com/questions/14165632/asp-net-mvc-4-for-loop-posts-model-collection-properties-but-foreach-does-not) – Simon MᶜKenzie Jul 14 '15 at 22:59

3 Answers3

3

Change the loop like so:

@for(int idx = 0;idx < Model.items.Count;idx++)
{
    <tr>
         <td>
            @Html.DisplayFor(_ => Model.items[idx].Label)
         </td>
         <td>
             @Html.TextBoxFor(_ => Model.items[idx].Field)
         </td>
     </tr>
}

The reason this works is that the <input> elements will be generated like so:

<input id="Items_0_" name="Items[0]" type="text" value="Foo"> <input id="Items_1_" name="Items[1]" type="text" value="Bar">

When you POST the data, something like the following will be in the body:

Items[0]:Foo
Items[1]:Bar

The default ASP.NET MVC model binder will pick these Items up from the request body and apply them to the viewmodel, in your case ModelInstrumentListingDetail.items

Jason Evans
  • 28,906
  • 14
  • 90
  • 154
  • This worked perfectly. Thank you so much; you saved me hours of work (and a lot of grief from my boss). Couple of questions for you: 1. This seems like a "hack"; is this just the way you have to do things in MVC to get it to work "correctly"? 2. Our office has switched to MVC from asp.net and no one here has any MVC training. I'd love to learn how to do things like this without having to post a question and hope someone answers it. I've read some books on MVC, but they usually just show the basics. Can you suggest any resources where I can learn things like what you've shown me? – boilers222 Jul 15 '15 at 12:50
  • 1
    Nope, this is not a hack, it's a pretty standard way of posting items to a controller in way the model binder knows they belong to a list. As for resources - most of what I know about MVC was picked up from StackOverflow. I asked questions and also looked at the answers given by Darin Dimitriov http://stackoverflow.com/users/29407/darin-dimitrov I learned a lot from his answers. Glad I could help :) – Jason Evans Jul 15 '15 at 13:16
1

You should use either EditorTemplates and EditorFor helper or for loop if you want to bind your list right. Here is an example.

If you will use for loop it should be like this:

  @for (int i = 0; i< Model.items.Count(); i++)
  {
   <tr>
      <td>
          @Html.DisplayFor(m => Model.items[i].Label)
      </td>
      <td>
          @Html.TextBoxFor(m => Model.items[i].Field)
      </td>
   </tr>
   }

for loop - short way, but using EditorTemplate - more clean solution, so I recommend it.

teo van kot
  • 12,350
  • 10
  • 38
  • 70
  • Thanks for the reply. The for loop work perfectly. I tried the EditorTemplate, but it didn't bind the values to the model (items was still null when it got to the controller). I suspect I'd need to use some combination of a for loop (instead of foreach) using the indexes on items and an EditorTemplate. I also am not completely sure I set up the EditorTemplate correctly. I set up a cshtml view of type ModelInstrumentListingItem where I had a textboxfor model.field. I then added a UIHint to ModelInstrumentListingItem. Think I need to investigate EditorTemplate more before I try using it. – boilers222 Jul 15 '15 at 13:18
0

I think I had a similar problem earlier.

Maybe try:

[HttpPost]
public ActionResult InstrumentListingAdd([Bind(Prefix = "item")]ModelInstrumentListingItem model, string submitInstrumentListingAdd)
{
  ...
}
FrostyStraw
  • 1,628
  • 3
  • 25
  • 34
  • Thanks for the reply. This didn't work for me. The HTML created uses the same name and id for every input tag. Maybe that's why it didn't work? Any thoughts on getting them to use different names? Here's the HTML: – boilers222 Jul 15 '15 at 12:54