2

I am just trying to display nth relation using ul and li in razor using recursive function call. Suppose I have db table where I store parent child relation like below one.

table structure

+----+----------+----------+
| ID | Name     | ParentID |
+----+----------+----------+
| 1  | Parent 1 | 0        |
+----+----------+----------+
| 2  | child 1  | 1        |
+----+----------+----------+
| 3  | child 2  | 1        |
+----+----------+----------+
| 4  | child 3  | 1        |
+----+----------+----------+
| 5  | Parent   | 0        |
+----+----------+----------+
| 6  | child 4  | 4        |
+----+----------+----------+

So i like to show the nested data this way in razor view

Parent 1
    child 1
    child 2
    child 3
        child 4
Parent

So this code i tried but could not achieve the goal.

c# POCO classes

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

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

Action code

    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<MenuDTO> mi = allMenu
        .Select(e => new
        {
            Id = e.Id,
            Name = e.Name,
            ParentId = e.ParentId,
            Children = allMenu.Where(x => x.ParentId == e.Id).ToList()
        }).ToList()
        .Select(p => new MenuDTO
        {
            Id = p.Id,
            Name = p.Name,
            ParentId = p.ParentId,
            Children = p.Children
            //Children = p.Children.Cast<MenuDTO>()
        }).ToList();

        ViewBag.menusList = mi;

        return View();
    }

Razor code

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


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

in my case i am query List allMenu to get data instead from db table. when i am running my code then i am getting below error

CS1502: The best overloaded method match for 'ASP._Page_Views_Menu_Index_cshtml.ShowTree(System.Collections.Generic.List)' has some invalid arguments

tell me what is wrong in my code. please help me to fix and acquire my goal. thanks

EDIT

Full working code as following

@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);
}

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)  /* grab second level children */
            .Select(e2 => new MenuItem
            {
                Id = e2.Id,
                Name = e2.Name,
                ParentId = e2.ParentId,
                Children = allMenu.Where(x2 => x2.ParentId == e2.Id).ToList() /* grab third level children */
            }).ToList()
    }).ToList();

    ViewBag.menusList = mi;

    return View();
}

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; }
}
1
  • ShowTree is expecting a list but you are sending the item @ShowTree(item.Children) . If you replace @ShowTree(item.Children) with @ShowTree(item) it will pass the list to the method. Commented Mar 20, 2018 at 19:56

2 Answers 2

2

I fixed and did the job this way

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.

working code sample

@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);
}

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();
}
Sign up to request clarification or add additional context in comments.

3 Comments

That looks great! I like your solution better. If the list came from the database, my code would be slower as it makes more round trips to the database for each set of children. Your code would make one query with the trade-off of hierarchy traversal after getting the results, which should be faster.
FYI, you no longer need the Children = allMenu.Where(x => x.ParentId == e.Id).ToList() line or the Children property at all. It will only slow your code.
yes you are right. i will comment those line. thanks for your help and support.
1

Your code is not able to find a function ShowTree taking a parameter of type ICollection<MenuItem> when it executes the line

@ShowTree(item.Children)

because item.Children is of type ICollection<MenuItem>. The function ShowTree in your code takes a parameter of a different type, List<MenuDTO>, which is not the same as ICollection<MenuItem>. As a result, the runtime reports the CS1502 error you see.

After realizing that you were looking for a recursive solution, I have modified the code to do just that.

Action Code

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 = GetChildren(allMenu, e.Id) /* Recursively grab the children */
    }).ToList();

    ViewBag.menusList = mi;

    return View();
}

/// <summary>
/// Recursively grabs the children from the list of items for the provided parentId
/// </summary>
/// <param name="items">List of all items</param>
/// <param name="parentId">Id of parent item</param>
/// <returns>List of children of parentId</returns>
private static List<MenuItem> GetChildren(List<MenuItem> items, int parentId)
{
    return items
        .Where(x => x.ParentId == parentId)
        .Select(e => new MenuItem
        {
            Id = e.Id,
            Name = e.Name,
            ParentId = e.ParentId,
            Children = GetChildren(items, e.Id)
        }).ToList();
}

Razor Code

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

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

5 Comments

there is one error in your code that is ShowTree(menuList); it should be @ ShowTree(menuList); then ShowTree will be called and write data in page. anyway thanks for your answer.
Good catch! Sorry, I didn't get a chance to test the code. I just wanted you to be aware that the algorithm may have left out child 4. If you know that there will be exactly three levels of menu items, then you can add another level of Where(...).Select {...}.ToList() clauses for the Children property of the current Select clause to cover the third menu level (where child 4 sits).
please look into the above code tell me what to change their as a result nested child should be shown. when i run my program then child 4 name is not showing which is child of child 3. what is the problem in code and tell me what to fix if you know. thanks
I went ahead and modified the query in your "Full working code". I'm new to contributing, so I hope you don't mind.
I finally realized that I was missing the recursive query part that you wanted, and I have added that to my solution. Please test and update your "Full working code" with the new changes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.