How to use two IENumerable models in one view

32,858

Looping over models can be a little confusing to begin with in MVC, only because the templated helpers (i.e. Html.DisplayFor and Html.EditorFor) can be provided templates which the helper will automatically invoke for every element in a collection. That means if you're new to MVC, and you don't realise a DisplayTemplate or an EditorTemplate has not been provided for the collection already, it looks as though a simple:

@Html.DisplayFor(m => m.SomePropertyThatHoldsACollection)

is all you need. So if you've seen something like that already, that might be why you made the assumption it would work. However, let's assume for a moment that a template has not been provided. You have two options.

Firstly, and most simply, would be to use foreach over the collection:

@foreach (var post in Model.PostsObject)
{
    @Html.DisplayFor(m => post.PostTitle)
    // display other properties
}

You could also use a for loop, but with IEnumerable<T>, there is no indexer, so this won't work:

@for (int i = 0; i < Model.PostsObject.Count(); i++)
{
    // This generates a compile-time error because
    // the index post[i] does not exist.
    // This syntax would work for a List<T> though.
    @Html.DisplayFor(m => post[i].PostTitle)
    // display other properties
}

If you did want to use the for loop still, you can use it like so:

@for (int i = 0; i < Model.PostsObject.Count(); i++)
{
    // This works correctly
    @Html.DisplayFor(m => post.ElementAt(i).PostTitle)
    // display other properties
}

So use whichever you prefer. However, at some point it would be a good idea to look into providing templates for your types. (Note: Although this article was written for MVC 2, the advice still applies.) They allow you to remove looping logic from your views, keeping them cleaner. When combined with Html.DisplayFor, or Html.EditorFor, they will also generate correct element naming for model binding (which is great). They also allow you to reuse presentation for a type.

One final comment I'd make is that the naming of your properties is a little verbose:

public class ModelMix
{
    public IEnumerable<Posts> PostsObject { get; set; }
    public IEnumerable<Album> ThreadsObject { get; set; }
}

We already know they're objects, so there's no need to add that on the end. This is more readable:

public class ModelMix
{
    public IEnumerable<Posts> Posts { get; set; }
    public IEnumerable<Album> Threads { get; set; }
}
Share:
32,858
user3163730
Author by

user3163730

Updated on July 18, 2022

Comments

  • user3163730
    user3163730 almost 2 years

    I am trying to use two models in one view, but from what I understand my program just don't see any objects within models.

    Here is my code.

    Models:

        public class Album
        {
            [Key]
            public int ThreadId { get; set; } 
            public int GenreId { get; set; }
            public string Title { get; set; }
            public string ThreadByUser { get; set; } 
            public string ThreadCreationDate { get; set; }
            public string ThreadContent { get; set; } 
            public Genre Genre { get; set; }
            public List<Posts> Posty { get; set; }
        }
        public class Posts 
        {
            [Key]
            public int PostId { get; set; }
            public int ThreadId { get; set; } 
            public string PostTitle { get; set; }
            public string PostContent { get; set; }
            public string PostDate { get; set; }
            public string PosterName { get; set; }
            public Album Album { get; set; }
    
        }
    
        public class ModelMix
        {
            public IEnumerable<Posts> PostsObject { get; set; }
            public IEnumerable<Album> ThreadsObject { get; set; }
        }  
    

    Index controller code:

        public ActionResult Index(int id)
        {
    
            ViewBag.ThreadId = id;
            var posts = db.Posts.Include(p => p.Album).ToList();
            var albums = db.Albums.Include(a => a.Genre).ToList();
    
            var mixmodel = new ModelMix
            {
                PostsObject = posts,
                ThreadsObject = albums
            };
    
            return View(mixmodel);
        }
    

    View code:

    @model MvcMusicStore.Models.ModelMix
    
    <h2>Index</h2>
    
    @Html.DisplayNameFor(model => model.PostsObject.PostContent)
    

    And when I try to execute my program I am getting this error:

    CS1061: The " System.Collections.Generic.IEnumerable 'does not contain a definition of" PostContent "not found method of expanding" PostContent ", which takes a first argument of type' System.Collections.Generic.IEnumerable "

    How I can make it work as intended? There are a lot of questions like mine on the internet but I couldn't find any matching my case.

  • Jon Douglas
    Jon Douglas over 10 years
    DisplayNameFor will only display titles and not actual data. These are usually for headers of tables.
  • Sergey Litvinov
    Sergey Litvinov over 10 years
    yep, i know. but original question was about DisplayNameFor method , so made sample with it
  • user3163730
    user3163730 over 10 years
    This applies when I have only one model, I do know how that works already (I think), I already completed tutorial from the that site (but for mvc4 - the one where you make movies database), but they didn't included any examples of having two models in one view.
  • user3163730
    user3163730 over 10 years
    Thank you very much, I went with foreach loop. I had no idea that solution for my problem will be that simple!
  • Jon Douglas
    Jon Douglas over 10 years
    You can simply just decide which Model you want from the complex object. I.E. Model.Object1.Whatever / Model.Object2.Whatever