0

I am trying to show nested data in ul/li, but nested children are not showing. See my code and please tell me what is wrong there.

Controller:

public ActionResult Index()
{       
    List<MenuItem> allMenu = new List<MenuItem>
    {
        new MenuItem {Id=1,Name="Parent 1", ParentId=0},
        new MenuItem {Id=2,Name="child 1", ParentId=1},
        new MenuItem {Id=3,Name="child 2", ParentId=1},
        new MenuItem {Id=4,Name="child 3", ParentId=1},
        new MenuItem {Id=5,Name="Parent 2", ParentId=0},
        new MenuItem {Id=6,Name="child 4", ParentId=4}
    };

    List<MenuItem> mi = allMenu
    .Where(e => e.ParentId == 0) /* grab only the root parent nodes */
    .Select(e => new MenuItem
    {
        Id = e.Id,
        Name = e.Name,
        ParentId = e.ParentId,
        Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
    }).ToList();

    ViewBag.menusList = mi;

    return View();
}

POCO class:

public class MenuItem 
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ParentId { get; set; }
    public virtual List<MenuItem> Children { get; set; }
}

View:

@helper ShowTree(List<Scaffolding.Controllers.MenuItem> menusList)
{
    <ul>
        @foreach (var item in menusList)
        {
            <li>
                <span>@item.Name</span>
                @if (item.Children != null && item.Children.Any())
                {
                    @ShowTree(item.Children)
                }
            </li>
        }
    </ul>
}

@{
    var menuList = ViewBag.menusList as List<Scaffolding.Controllers.MenuItem>;
    @ShowTree(menuList);
}

If you run the code then you will see child 4 is not showing which is a child of child 3. Please advise what I need to change in my code. Thanks

RickL
  • 3,318
  • 10
  • 38
  • 39
Mist
  • 684
  • 9
  • 30
  • 1
    possible duplicate of https://stackoverflow.com/questions/49392969/asp-net-mvc-how-to-show-nested-parent-child-relation-using-recursive-technique – user9405863 Mar 22 '18 at 16:20
  • If `"child 4"` is a child of `"child 3"` but `"child 3"` is itself a child (and therefore has a `ParentId` that is not zero then this line `.Where(e => e.ParentId == 0)` will filter it out and it never gets its `Children` property populated like you need. – Shelby115 Mar 22 '18 at 16:48
  • Additionally, even if you removed the where clause you'd still have the issue that you'd need to make multiple passes at it to get the MenuItems recursively nested like you need properly. – Shelby115 Mar 22 '18 at 16:52
  • @Shelby115 can you please rectify my code which will work for nth nesting because nothing comes to mind like where to change in code. thanks – Mist Mar 22 '18 at 19:51

2 Answers2

1

Your query gets the top level elements (ParentId == 0) only and then populate just their direct child elements.

Your query needs to be changed to populate all child elements for all levels. Note that your MeuItem does not need the ParentId property.

// Group the items by parentId and project to MenuItem
var groups = allMenu.ToLookup(x => x.ParentId, x => new MenuItem
{
    Id = x.Id,
    Name = x.Name,
});
// Assign the child menus to all items
foreach (var item in allMenu)
{
    item.children = groups[item.Id].ToList();
}
// Return just the top level items
ViewBag.menusList = groups[0].ToList();

As a side note, do not use ViewBag. Pass the model to the view instead

return View(groups[0].ToList());

and in the view

@model List<MenuItem>
....
@ShowTree(Model);
  • thanks for your answer sir. now i could fix the problem. problem was main in razor logic which i could fix and take help from this link https://www.mikesdotnetting.com/article/208/practical-recursion-in-asp-net-web-pages-with-webmatrix thanks a lot for your suggestion and support. – Mist Mar 22 '18 at 21:26
  • You do not need to change the `@helper` code in your original question (and your own answer makes no sense and is not even correct) –  Mar 22 '18 at 21:28
  • sir i just checked the code and now it is working. please come with a example that why you are saying not correct if possible ? – Mist Mar 22 '18 at 21:31
  • You did not even find this answer useful! (but the `@helper` code in your question does not need to be changed) –  Mar 22 '18 at 21:35
  • please tell me what ToLookup() function does in EF? – Mist Mar 27 '18 at 12:23
  • 1
    Its effectively a `.GroupBy()` which is executed immediately - refer [this answer](https://stackoverflow.com/questions/13739462/lookup-vs-groupby) for a detailed explanation –  Mar 27 '18 at 22:44
0

Now i could fix my problem. the problem was in logic of razor code and also i comment this line //.Where(e => e.ParentId == 0) here i am adding working code.

@helper  ShowTree(List<NestedChild.Controllers.MenuItem> menu, int? parentid = 0, int level = 0)
{
    var items = menu.Where(m => m.ParentId == parentid);

    if (items.Any())
    {
        if (items.First().ParentId > 0)
        {
            level++;
        }

        <ul>
            @foreach (var item in items)
            {
            <li>
                @item.Name
            </li>
                @ShowTree(menu, item.Id, level);
            }
        </ul>
    }
}
@{
    var menuList = ViewBag.menusList as List<NestedChild.Controllers.MenuItem>;
    @ShowTree(menuList);
}

Action

public ActionResult Index()
{
    List<MenuItem> allMenu = new List<MenuItem>
    {
        new MenuItem {Id=1,Name="Parent 1", ParentId=0},
        new MenuItem {Id=2,Name="child 1", ParentId=1},
        new MenuItem {Id=3,Name="child 2", ParentId=1},
        new MenuItem {Id=4,Name="child 3", ParentId=1},
        new MenuItem {Id=5,Name="Parent 2", ParentId=0},
        new MenuItem {Id=6,Name="child 4", ParentId=4}
    };

    List<MenuItem> mi = allMenu
    //.Where(e => e.ParentId == 0) /* grab only the root parent nodes */
    .Select(e => new MenuItem
    {
        Id = e.Id,
        Name = e.Name,
        ParentId = e.ParentId,
        Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
    }).ToList();

    ViewBag.menusList = mi;

    return View();
}
Mist
  • 684
  • 9
  • 30