Foreach in a Foreach in MVC View

145,415

Solution 1

Assuming your controller's action method is something like this:

public ActionResult AllCategories(int id = 0)
{
    return View(db.Categories.Include(p => p.Products).ToList());
}

Modify your models to be something like this:

public class Product
{
    [Key]
    public int ID { get; set; }
    public int CategoryID { get; set; }
    //new code
    public virtual Category Category { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Path { get; set; }

    //remove code below
    //public virtual ICollection<Category> Categories { get; set; }
}

public class Category
{
    [Key]
    public int CategoryID { get; set; }
    public string Name { get; set; }
    //new code
    public virtual ICollection<Product> Products{ get; set; }
}

Then your since now the controller takes in a Category as Model (instead of a Product):

foreach (var category in Model)
{
    <h3><u>@category.Name</u></h3>
    <div>
        <ul>    
            @foreach (var product in Model.Products)
            {
                // cut for brevity, need to add back more code from original
                <li>@product.Title</li>
            }
        </ul>
    </div>
}

UPDATED: Add ToList() to the controller return statement.

Solution 2

You have:

foreach (var category in Model.Categories)

and then

@foreach (var product in Model)

Based on that view and model it seems that Model is of type Product if yes then the second foreach is not valid. Actually the first one could be the one that is invalid if you return a collection of Product.

UPDATE:

You are right, I am returning the model of type Product. Also, I do understand what is wrong now that you've pointed it out. How am I supposed to do what I'm trying to do then if I can't do it this way?

I'm surprised your code compiles when you said you are returning a model of Product type. Here's how you can do it:

@foreach (var category in Model)
{
    <h3><u>@category.Name</u></h3>

    <div>
        <ul>    
            @foreach (var product in category.Products)
            {
                <li>
                    put the rest of your code
                </li>
            }
        </ul>
    </div>
}

That suggest that instead of returning a Product, you return a collection of Category with Products. Something like this in EF:

// I am typing it here directly 
// so I'm not sure if this is the correct syntax.
// I assume you know how to do this,
// anyway this should give you an idea.
context.Categories.Include(o=>o.Product)

Solution 3

Try this:

It looks like you are looping for every product each time, now this is looping for each product that has the same category ID as the current category being looped

<div id="accordion1" style="text-align:justify">
@using (Html.BeginForm())
{
    foreach (var category in Model.Categories)
    {
        <h3><u>@category.Name</u></h3>

        <div>
            <ul>    
                @foreach (var product in Model.Product.Where(m=> m.CategoryID= category.CategoryID)
                {
                    <li>
                        @product.Title
                        @if (System.Web.Security.UrlAuthorizationModule.CheckUrlAccessForPrincipal("/admin", User, "GET"))
                        {
                            @Html.Raw(" - ")  
                            @Html.ActionLink("Edit", "Edit", new { id = product.ID })
                        }
                        <ul>
                            <li>
                                @product.Description
                            </li>
                        </ul>
                    </li>
                }
            </ul>
        </div>
    }
}  

Share:
145,415
Guillaume Longtin
Author by

Guillaume Longtin

Updated on August 03, 2022

Comments

  • Guillaume Longtin
    Guillaume Longtin over 1 year

    BIG EDIT : I have edited my full post with the answer that I came up with the help of Von V and Johannes, A BIG THANK YOU GUYS !!!!

    I've been trying to do a foreach loop inside a foreach loop in my index view to display my products in an accordion. Let me show you how I'm trying to do this.

    Here are my models :

    public class Product
    {
        [Key]
        public int ID { get; set; }
    
        public int CategoryID { get; set; }
    
        public string Title { get; set; }
    
        public string Description { get; set; }
    
        public string Path { get; set; }
    
        public virtual Category Category { get; set; }
    }
    
    public class Category
    {
        [Key]
        public int CategoryID { get; set; }
    
        public string Name { get; set; }
    
        public virtual ICollection<Product> Products { get; set; }
    }
    

    It's a one-one one-many relationship, One product has only one category but a category had many products.

    Here is what I'm trying to do in my view :

    @model IEnumerable<MyPersonalProject.Models.Product>   
    
        <div id="accordion1" style="text-align:justify">
        @foreach (var category in ViewBag.Categories)
        {
            <h3><u>@category.Name</u></h3>
    
            <div>
    
                @foreach (var product in Model)
                {
                    if (product.CategoryID == category.CategoryID)
                    {
                        <table cellpadding="5" cellspacing"5" style="border:1px solid black; width:100%;background-color:White;">
                            <thead>
                                <tr>
                                    <th style="background-color:black; color:white;">
                                        @product.Title  
                                        @if (System.Web.Security.UrlAuthorizationModule.CheckUrlAccessForPrincipal("/admin", User, "GET"))
                                        {
                                            @Html.Raw(" - ")  
                                            @Html.ActionLink("Edit", "Edit", new { id = product.ID }, new { style = "background-color:black; color:white !important;" })
                                        }
                                    </th>
                                </tr>
                            </thead>
    
                            <tbody>
                                <tr>
                                    <td style="background-color:White;">
                                        @product.Description
                                    </td>
                                </tr>
                            </tbody>      
                        </table>                       
                    }
                }
    
            </div>
        }  
    </div>
    

    I'm not quite sure this is the right way of doing it but this is pretty much what I'm trying to do. Foreach categories, put all products of that categories inside an accordion tab.

    • category 1
      • product 1
      • product 3
    • category 2
      • product 2
      • product 4
    • category 3
      • product 5

    Here I will add my mapping for my one-one one-many (Thanks Brian P) relationship :

    public class MyPersonalProjectContext : DbContext
    {
        public DbSet<Product> Product { get; set; }
        public DbSet<Category> Category { get; set; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    
            modelBuilder.Entity<Product>();
            modelBuilder.Entity<Category>();
        }
    }
    

    I will also add my controller so you can see how I did it :

    public ActionResult Index()
        {
            ViewBag.Categories = db.Category.OrderBy(c => c.Name).ToList();
            return View(db.Product.Include(c => c.Category).ToList());
        }
    

    BIG EDIT : I have edited my full post with the answer that I came up with the help of Von V and Johannes, A BIG THANK YOU GUYS !!!!

  • Guillaume Longtin
    Guillaume Longtin about 11 years
    I tried going for something like this a while ago, however for me if I do Model. => all I can access is the value of my Product model. I can't go for Model.Product.
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    It should be up right now, You are right, I am returning the model of type Product. Also, I do understand what is wrong now that you've pointed it out. How am I supposed to do what I'm trying to do then if I can't do it this way?
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    If I go for something like this, which seems pretty good. I will have to modify my mapping. Let me update my code with my mapping so you can comment on it.
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    Alright I'm going for something like your answer. Let me get you back on this.
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    The only thing not working out for me is my mapping, not sure it is done right. Otherwise with the way you're doing it, it will work.
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    Any thoughts on my mapping?
  • Johannes Setiabudi
    Johannes Setiabudi about 11 years
    your mapping seems backward to me, since it's a category can have many products and each product can only have 1 category. I'd advice to correct your mapping. Why are you creating custom mapping instead of using the default EF mapping?
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    Thought I had to specify it. If it can work out this way I wont modify it for my personal knowledge. But my problem ATM is when I try to call my first foreach loop it always says I can't do it since Model.Category doesn't have get enumerator.
  • Johannes Setiabudi
    Johannes Setiabudi about 11 years
    If you are following my example - which should not require custom mapping, Category is a single object (not a collection) as a property or Product. Remember that the controller action returns a collection of Category (not Product).
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    By doing exactly the same thing as you did, and calling @model MyPersonalProject.Category in my index view. Doing foreach(var category in Model) gives me an error saying it does not contain a public definition GetEnumerator.
  • Johannes Setiabudi
    Johannes Setiabudi about 11 years
    I have revised my code - added "ToList()" to my return statement in the controller.
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    Somehow I made it, I will update my post. Your answer and @von v answer helped very much.
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    Edited my post with my solution, what do you think?
  • Guillaume Longtin
    Guillaume Longtin about 11 years
    Thank you for your help I made it somehow. Edited my post with my solution, what do you think?