Model Binding to a List MVC 4
Asked Answered
E

3

31

Is there a pattern to bind an IList of items to the view. I seem to be having issues with the HttpPost. I know Phil Haack wrote a nice article but it is dated and he said they might have a fix with MVC 4.

Earlearla answered 13/3, 2013 at 2:6 Comment(10)
Please consider adding more detail, including relevant code and links to your question to make it clearer. Here's a good checklist to get you started: tinyurl.com/so-listMidian
Binding a list to a view. What is not clear about this?Earlearla
There are variations depending on what your form looks like. Some View code would help.Ettieettinger
Collections are not persisted on postback.Sapajou
Selecting a list of items, or a form that has a textbox for each item, or a form that has a textbox for each property of each item, all possible variations on "binding a list to a view" and require different techniques.Ettieettinger
You said you have issues with the HttpPost. Apparently that means you have some code with a specific problem, care to show it? You've made reference to some article by Phil Haack, but no link. There isn't only one way to "bind a list of items to a view". Don't presume upon the generosity of the community who is answering your question.Midian
Here's the the url with Phil's article. haacked.com/archive/2008/10/23/model-binding-to-a-list.aspxEarlearla
I'm trying to keep it as simple as possible. There's nothing wrong with my code, it's more of an MVC issue. The issue is Model Binding a List. There are endless permutations, say it's a simple List of Cars.Earlearla
@JT refer to the answer here: #7009214Latea
@JT We didn't say anything was wrong with your code. We need to see your viewmodel+view attempt to know what list binding technique will work for your scenario.Ettieettinger
E
58

This is how I do it if I need a form displayed for each item, and inputs for various properties. Really depends on what I'm trying to do though.

ViewModel looks like this:

public class MyViewModel
{
   public List<Person> Persons{get;set;}
}

View(with BeginForm of course):

@model MyViewModel


@for( int i = 0; i < Model.Persons.Count(); ++i)
{
    @Html.HiddenFor(m => m.Persons[i].PersonId)
    @Html.EditorFor(m => m.Persons[i].FirstName) 
    @Html.EditorFor(m => m.Persons[i].LastName)         
}

Action:

[HttpPost]public ViewResult(MyViewModel vm)
{
...

Note that on post back only properties which had inputs available will have values. I.e., if Person had a .SSN property, it would not be available in the post action because it wasn't a field in the form.

Note that the way MVC's model binding works, it will only look for consecutive ID's. So doing something like this where you conditionally hide an item will cause it to not bind any data after the 5th item, because once it encounters a gap in the IDs, it will stop binding. Even if there were 10 people, you would only get the first 4 on the postback:

@for( int i = 0; i < Model.Persons.Count(); ++i)
{
    if(i != 4)//conditionally hide 5th item, 
    { //but BUG occurs on postback, all items after 5th will not be bound to the the list
      @Html.HiddenFor(m => m.Persons[i].PersonId)
      @Html.EditorFor(m => m.Persons[i].FirstName) 
      @Html.EditorFor(m => m.Persons[i].LastName)           
    }
}
Ettieettinger answered 13/3, 2013 at 2:25 Comment(5)
I use mvc 5.1 and in stead of ++1 i had to do it like this: @{ ++i; }, inside a @foreach() block.Laryngo
@Laryngo Perhaps, it might have also been if you had HTML tags, you would need the @{ } to switch that context back to C# code. But good tip either way.Ettieettinger
Declaring "i" and then using a foreach is pretty ugly especially when we have FOR LOOPS. Also please do not use a specific List implementation unless you really need to.Scholl
Good point, sometimes I can't use EditorFor and must build the <input> and like the ease of having the iterator variable still as it feels very non-DRY to repeat the index access all over the place. In this example there is no use for the person variable so I've switched from foreach to for.Ettieettinger
Can we do it using asp tag helpers ?Monochloride
S
11

A clean solution could be create a generic class to handle the list, so you don't need to create a different class each time you need it.

public class ListModel<T>
{
    public List<T> Items { get; set; }

    public ListModel(List<T> list) {
        Items = list;
    }
}

and when you return the View you just need to simply do:

List<customClass> ListOfCustomClass = new List<customClass>();
//Do as needed...
return View(new ListModel<customClass>(ListOfCustomClass));

then define the list in the model:

@model ListModel<customClass>

and ready to go:

@foreach(var element in Model.Items) {
  //do as needed...
}
Sundstrom answered 16/4, 2014 at 19:55 Comment(0)
C
4

~Controller

namespace ListBindingTest.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            List<String> tmp = new List<String>();
            tmp.Add("one");
            tmp.Add("two");
            tmp.Add("Three");
            return View(tmp);
        }

        [HttpPost]
        public ActionResult Send(IList<String> input)
        {
            return View(input);
        }    
    }
}

~ Strongly Typed Index View

@model IList<String>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
    <div>
    @using(Html.BeginForm("Send", "Home", "POST"))
    {
        @Html.EditorFor(x => x)
        <br />
        <input type="submit" value="Send" />
    }
    </div>
</body>
</html>

~ Strongly Typed Send View

@model IList<String>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Send</title>
</head>
<body>
    <div>
    @foreach(var element in @Model)
    {
        @element
        <br />
    }
    </div>
</body>
</html>

This is all that you had to do man, change his MyViewModel model to IList.

Cronyism answered 13/3, 2013 at 3:28 Comment(3)
Steve, thanks for the info, this works fine. My issue, perhaps I didn't explain was Binding a dynamic List. Sanderson nails it in this post blog.stevensanderson.com/2010/01/28/…Earlearla
@JT It doesn't matter if I statically type the list in the controller, or grab it from a database or a file. Where the list comes from is irrelevant to model binding.Ranchman
My issue was with HttpPost. When a list of objects changes in the view, the values do not get posted correctly. This is what Sanderson addressed and I'm using his method. The MVC team knows about this and Sanderson's method is the best I've seen. I apologize to everyone for not being clear.Earlearla

© 2022 - 2024 — McMap. All rights reserved.