Asp.Net Core MVC项目三级嵌套子菜单无法生成问题求助
Asp.Net Core MVC三级嵌套导航菜单第三层不显示问题修复
开发Asp.Net Core MVC项目的三级嵌套导航菜单时,仅能显示顶层和中间层菜单,第三层(名为bottom)菜单未展示。以下是现有实现代码及修复方案:
现有代码
SQL表结构(MenuMasterDemo)
表包含以下字段:
MenuID:bigint类型,主键Menu:varchar类型,菜单名称ParentMenuID:bigint类型,父菜单ID(顶层菜单为NULL)
数据示例:
| MenuID | Menu | ParentMenuID |
|---|---|---|
| 1 | Top | NULL |
| 2 | Middle | 1 |
| 3 | Bottom | 2 |
MenuMasterDemo.cs
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
相关产品推荐
相关产品推荐

