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.

tereško
  • 58,060
  • 25
  • 98
  • 150
Karthik
  • 377
  • 1
  • 5
  • 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: http://tinyurl.com/so-list – mellamokb Mar 13 '13 at 02:09
  • Binding a list to a view. What is not clear about this? – Karthik Mar 13 '13 at 02:17
  • 1
    There are variations depending on what your form looks like. Some View code would help. – AaronLS Mar 13 '13 at 02:17
  • Collections are not persisted on postback. – Dustin Kingen Mar 13 '13 at 02:18
  • 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. – AaronLS Mar 13 '13 at 02:19
  • 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. – mellamokb Mar 13 '13 at 02:19
  • 1
    Here's the the url with Phil's article. http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx – Karthik Mar 13 '13 at 02:23
  • 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. – Karthik Mar 13 '13 at 02:25
  • 1
    @JT refer to the answer here: http://stackoverflow.com/questions/7008714/passing-ienumerable-or-list-model-to-controller-using-httppost/7009837#7009837 – lahsrah Mar 13 '13 at 02:31
  • @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. – AaronLS Mar 13 '13 at 02:49

3 Answers3

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)           
    }
}
AaronLS
  • 37,329
  • 20
  • 143
  • 202
  • 1
    I use mvc 5.1 and in stead of `++1` i had to do it like this: `@{ ++i; }`, inside a `@foreach()` block. – Yustme Mar 03 '14 at 15:58
  • @Yustme 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. – AaronLS Mar 05 '14 at 08:44
  • 6
    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. – Rush Frisby Apr 14 '14 at 16:21
  • Good point, sometimes I can't use EditorFor and must build the `` 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. – AaronLS May 01 '14 at 01:41
  • Can we do it using asp tag helpers ? – Arpit A. Mar 06 '22 at 08:44
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...
}
Javi
  • 378
  • 2
  • 9
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.

Steve's a D
  • 3,801
  • 10
  • 39
  • 60
  • 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 http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ – Karthik Mar 13 '13 at 03:36
  • @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. – Steve's a D Mar 13 '13 at 03:38
  • 1
    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. – Karthik Mar 13 '13 at 04:02