You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Asp.Net Core MVC项目三级嵌套子菜单无法生成问题求助

Asp.Net Core MVC三级嵌套导航菜单第三层不显示问题修复

开发Asp.Net Core MVC项目的三级嵌套导航菜单时,仅能显示顶层和中间层菜单,第三层(名为bottom)菜单未展示。以下是现有实现代码及修复方案:

现有代码

SQL表结构(MenuMasterDemo)

表包含以下字段:

  • MenuID:bigint类型,主键
  • Menu:varchar类型,菜单名称
  • ParentMenuID:bigint类型,父菜单ID(顶层菜单为NULL)

数据示例:

MenuIDMenuParentMenuID
1TopNULL
2Middle1
3Bottom2
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace SampleApp.Models;

[Table("MenuMasterDemo")]
public partial class MenuMasterDemo
{
    [Key]
    [Column("MenuID")]
    public long MenuId { get; set; }

    [Unicode(false)]
    public string? Menu { get; set; }

    [Column("ParentMenuID")]
    public long? ParentMenuId { get; set; }

    // 存储子项的属性
    [NotMapped]
    public List<MenuMasterDemo>? Children { get; set; }
}

AppMenuViewComponent

using Microsoft.AspNetCore.Mvc;
using SampleApp.Data;

namespace SampleApp.ViewComponents
{
    public class AppMenuViewComponent : ViewComponent
    {
        private readonly SampleContext _context;

        public AppMenuViewComponent(SampleContext context)
        {
            _context = context;
        }
        public IViewComponentResult Invoke()
        {
            return View(_context.MenuMasterDemos.ToList());
        }
    }
}

AppMenuViewComponent - Default.cshtml

@model IEnumerable<MenuMasterDemo>

<!-- 侧边导航菜单 -->
<div class="d-flex flex-column align-items-start align-items-md-center px-3 pt-2 text-white">
    <ul class="nav flex-column nav-pills">
        @foreach (var item in Model.Where(x => x.ParentMenuId == null)) // 筛选顶层菜单
        {
            <li class="nav-item">
                <a class="nav-link" data-bs-toggle="collapse" href="#submenu-@item.MenuId" role="button" aria-expanded="false" aria-controls="submenu-@item.MenuId">@item.Menu</a>
                <div class="collapse" id="submenu-@item.MenuId">
                    <ul class="nav flex-column ms-3">
                        <!-- 为每个顶层菜单项调用RenderSubMenuItemsViewComponent -->
                        @await Component.InvokeAsync("RenderSubMenuItems", new { model = Model, parentMenuId = item.MenuId })
                    </ul>
                </div>
            </li>
        }
    </ul>
</div>

RenderSubMenuItemsViewComponent

using Microsoft.AspNetCore.Mvc;
using SampleApp.Data;
using SampleApp.Models;

namespace SampleApp.ViewComponents
{
    public class RenderSubMenuItemsViewComponent : ViewComponent
    {
        private readonly SampleContext _context;

        public RenderSubMenuItemsViewComponent(SampleContext context)
        {
            _context = context;
        }
        public IViewComponentResult Invoke(IEnumerable<MenuMasterDemo> model, long parentMenuId)
        {
            var submenuItems = model.Where(x => x.ParentMenuId == parentMenuId).ToList();

            foreach (var submenuItem in submenuItems)
            {
                // 错误:Invoke返回的是ViewComponentResult,无法转换为List<MenuMasterDemo>,导致Children为null
                submenuItem.Children = Invoke(model, submenuItem.MenuId) as List<MenuMasterDemo>;
            }

            return View(submenuItems);
        }
    }
}

RenderSubMenuItemsViewComponent - Default.cshtml

@model IEnumerable<MenuMasterDemo>

<ul class="nav flex-column ms-3">
    @foreach (var subItem in Model)
    {
        <li class="nav-item">
            <a class="nav-link" href="#">@subItem.Menu</a>
            @if (subItem.Children != null && subItem.Children.Any())
            {
                <!-- 递归渲染子项 -->
                @await Component.InvokeAsync("RenderSubMenuItems", new { model = subItem.Children, parentMenuId = subItem.MenuId })
            }
        </li>
    }
</ul>

_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - SampleApp</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/SampleApp.styles.css" asp-append-version="true" />

</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <div>
                @await Component.InvokeAsync("AppMenu")
            </div>
            <div class="col py-3">
                @RenderBody()
            </div>
        </div>
    </div>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true" />
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

问题分析

核心错误出在RenderSubMenuItemsViewComponent的Invoke方法中:递归调用Invoke(model, submenuItem.MenuId)返回的是IViewComponentResult视图组件结果,而非直接的菜单列表,将其强制转换为List<MenuMasterDemo>会得到null,导致第三层菜单的Children属性始终为空,无法触发后续递归渲染。

修复方案

方案1:提前构建完整层级菜单(推荐)

在AppMenuViewComponent中一次性构建好带层级的菜单数据,再传递给视图,避免在视图组件中错误递归:

修改AppMenuViewComponent

using Microsoft.AspNetCore.Mvc;
using SampleApp.Data;
using SampleApp.Models;
using System.Linq;

namespace SampleApp.ViewComponents
{
    public class AppMenuViewComponent : ViewComponent
    {
        private readonly SampleContext _context;

        public AppMenuViewComponent(SampleContext context)
        {
            _context = context;
        }

        public IViewComponentResult Invoke()
        {
            var allMenus = _context.MenuMasterDemos.ToList();
            // 构建层级菜单
            var menuTree = BuildMenuTree(allMenus, null);
            return View(menuTree);
        }

        // 递归构建菜单树
        private List<MenuMasterDemo> BuildMenuTree(List<MenuMasterDemo> allMenus, long? parentId)
        {
            return allMenus
                .Where(m => m.ParentMenuId == parentId)
                .Select(m => new MenuMasterDemo
                {
                    MenuId = m.MenuId,
                    Menu = m.Menu,
                    ParentMenuId = m.ParentMenuId,
                    Children = BuildMenuTree(allMenus, m.MenuId)
                })
                .ToList();
        }
    }
}

修改AppMenuViewComponent - Default.cshtml

不需要再传递整个列表,直接使用已构建好的层级数据:

@model List<MenuMasterDemo>

<!-- 侧边导航菜单 -->
<div class="d-flex flex-column align-items-start align-items-md-center px-3 pt-2 text-white">
    <ul class="nav flex-column nav-pills">
        @foreach (var item in Model)
        {
            <li class="nav-item">
                <a class="nav-link" data-bs-toggle="collapse" href="#submenu-@item.MenuId" role="button" aria-expanded="false" aria-controls="submenu-@item.MenuId">@item.Menu</a>
                <div class="collapse" id="submenu-@item.MenuId">
                    <!-- 递归渲染子菜单 -->
                    @await Component.InvokeAsync("RenderSubMenuItems", new { model = item.Children })
                </div>
            </li>
        }
    </ul>
</div>

修改RenderSubMenuItemsViewComponent

简化逻辑,直接接收子菜单列表:

using Microsoft.AspNetCore.Mvc;
using SampleApp.Models;

namespace SampleApp.ViewComponents
{
    public class RenderSubMenuItemsViewComponent : ViewComponent
    {
        public IViewComponentResult Invoke(List<MenuMasterDemo> model)
        {
            // 空列表直接返回空视图
            if (model == null || !model.Any())
                return View(new List<MenuMasterDemo>());
            
            return View(model);
        }
    }
}

修改RenderSubMenuItemsViewComponent - Default.cshtml

调整递归调用逻辑,直接传递当前项的Children:

@model List<MenuMasterDemo>

@if (Model.Any())
{
    <ul class="nav flex-column ms-3">
        @foreach (var subItem in Model)
        {
            <li class="nav-item">
                <a class="nav-link" href="#">@subItem.Menu</a>
                @if (subItem.Children != null && subItem.Children.Any())
                {
                    <!-- 递归渲染子项 -->
                    @await Component.InvokeAsync("RenderSubMenuItems", new { model = subItem.Children })
                }
            </li>
        }
    </ul>
}

方案2:修复原视图组件的递归逻辑

如果不想重构层级构建,可单独写一个获取子项的方法,避免调用视图组件的Invoke方法:

修改RenderSubMenuItemsViewComponent

using Microsoft.AspNetCore.Mvc;
using SampleApp.Models;
using System.Collections.Generic;
using System.Linq;

namespace SampleApp.ViewComponents
{
    public class RenderSubMenuItemsViewComponent : ViewComponent
    {
        public IViewComponentResult Invoke(IEnumerable<MenuMasterDemo> model, long parentMenuId)
        {
            var submenuItems = model.Where(x => x.ParentMenuId == parentMenuId).ToList();

            foreach (var submenuItem in submenuItems)
            {
                // 使用单独的方法获取子项,而非调用Invoke
                submenuItem.Children = GetSubMenuItems(model, submenuItem.MenuId);
            }

            return View(submenuItems);
        }

        // 递归获取子菜单列表
        private List<MenuMasterDemo> GetSubMenuItems(IEnumerable<MenuMasterDemo> model, long parentMenuId)
        {
            var items = model.Where(x => x.ParentMenuId == parentMenuId).ToList();
            foreach (var item in items)
            {
                item.Children = GetSubMenuItems(model, item.MenuId);
            }
            return items;
        }
    }
}

视图文件无需修改,这样第三层菜单的Children就能正确赋值,从而正常显示。

内容的提问来源于stack exchange,提问作者beard_less_coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 18:09:51