Search code examples
recursionrazorasp.net-coreasp.net-core-viewcomponent

Recursive ViewComponent causes infinite loop


I'm trying to make a recursive category tree using a ViewComponent, but I seem to get stuck in an infinite loop, and end up with HTTP Error 502.3 - Bad Gateway after a minute or two.

My thinking has been like this:

  1. In the main View, the component is invoked with an empty List<ViewModelProductCategory> as a parameter, to indicate that I want to get the root categories first.

  2. The ViewComponent query the database for categories with ParentId == null and returns it to Default.cshtml.

  3. In Default.cshtml, the component is invoked again, but this time with a list of children categories as parameter.

I'm invoking the ViewComponent like this:

@await Component.InvokeAsync("SelectCategories", 
        new List<MyStore.Models.ViewModels.ViewModelProductCategory> { })

The ViewComponent class looks like this:

using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyStore.Models;
using MyStore.Models.ViewModels;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyStore.Areas.Admin.ViewComponents
{
    public class SelectCategoriesViewComponent : ViewComponent
    {
        private readonly MyStoreContext _context;
        private readonly IMapper _mapper;

        public SelectCategoriesViewComponent(MyStoreContext context, IMapper mapper)
        {
            _context = context;
            _mapper = mapper;
        }

        public async Task<IViewComponentResult> InvokeAsync(List<ViewModelProductCategory> catList)
        {
            List<ViewModelProductCategory> VM = await GetCategoriesAsync(catList);
            return View(VM);
        }

        private async Task<List<ViewModelProductCategory>> GetCategoriesAsync(List<ViewModelProductCategory> catList)
        {
            List<ViewModelProductCategory> VM = new List<ViewModelProductCategory>();
            if (catList.Count() == 0)
            {
                VM = _mapper.Map<List<ViewModelProductCategory>>
                    (await _context.ProductCategories
                    .Where(x => x.ParentId == null)
                    .ToListAsync());
            }
            else
            {
                VM = catList;
            }
            return VM;
        }
    }
}

The ViewComponent's Default.cshtml looks like this:

@model IEnumerable<MyStore.Models.ViewModels.ViewModelProductCategory>
<ul style="list-style:none;padding-left:0px;">
    @if (Model != null)
    {
        foreach (var item in Model)
        {
            <li style="margin-top:4px;padding:0px;">
                @Html.HiddenFor(m => item.Id)
                @Html.CheckBoxFor(m => item.Checked)
                @Html.LabelFor(m => item.Id, item.Title)
                <ul>
                    @await Component.InvokeAsync("SelectCategories", item.Children)
                </ul>
            </li>
        }
    }
</ul>

Where is/are the error/s?

Edit

The comment from Tarek.Eladly made me realize I had a bad logic flaw in the GetCategoriesAsync-method. I have made these changes to my code, and now it works:

Invoking the ViewComponent first time:

@await Component.InvokeAsync("SelectCategories",
new
{
    root = true,
    catList = new List<MyStore.Models.ViewModels.ViewModelProductCategory>()
})

The ViewComponent-methods:

public async Task<IViewComponentResult> InvokeAsync(bool root, List<ViewModelProductCategory> catList)
{
    List<ViewModelProductCategory> VM = await GetCategoriesAsync(root, catList);
    return View(VM);
}

private async Task<List<ViewModelProductCategory>> GetCategoriesAsync(bool root, List<ViewModelProductCategory> catList)
{
    List<ViewModelProductCategory> VM = new List<ViewModelProductCategory>();
    if (root)
    {
        VM = _mapper.Map<List<ViewModelProductCategory>>
            (await _context.ProductCategories
            .Where(x => x.ParentId == null)
            .ToListAsync());
    }
    else
    {
        VM = catList;
    }
    return VM;
}

Invoking the ViewComponent in Default.cshtml:

@await Component.InvokeAsync("SelectCategories",
new
{
    root = false,
    catList = item.Children
})

:)


Solution

  • if i am right just take a look at (.Where(x => x.ParentId == null)) in GetCategoriesAsync you always get the root category and never check anything else.