記事詳細


投稿日
2026年09月28日19時25分
タイトル

ASP.NET Core MVC: MVCアプリでメニューを階層ドロップダウンで表示する方法を教えてください。

内容

了解です。
前回の階層ドロップダウンメニュー実装例を、さらに 実務向けの強化版 に改良します。
今回は Bootstrap 5 をベースに、多階層対応 + アクティブ強調 + アイコン対応 + ホバー展開 + アニメーション を組み込みます。

1. モデル(自己参照型 + アイコン + 表示順)

Csharp
// Models/MenuItem.cs using System.Collections.Generic; namespace YourApp.Models { public class MenuItem { public int Id { get; set; } public string Title { get; set; } public string Url { get; set; } public int? ParentId { get; set; } public int DisplayOrder { get; set; } // 表示順 public List<MenuItem> Children { get; set; } = new(); public bool IsActive { get; set; } public string IconClass { get; set; } } }

2. コントローラー(階層構築 + アクティブ判定 + 並び替え)

Csharp
// Controllers/MenuController.cs using Microsoft.AspNetCore.Mvc; using YourApp.Models; using System.Collections.Generic; using System.Linq; namespace YourApp.Controllers { public class MenuController : Controller { public IActionResult Index() { var flatMenu = new List<MenuItem> { new MenuItem { Id = 1, Title = "ホーム", Url = "/", IconClass = "fa fa-home", DisplayOrder = 1 }, new MenuItem { Id = 2, Title = "製品", Url = "/products", IconClass = "fa fa-box", DisplayOrder = 2 }, new MenuItem { Id = 3, Title = "サービス", Url = "/services", IconClass = "fa fa-cogs", DisplayOrder = 3 }, new MenuItem { Id = 4, Title = "製品A", Url = "/products/a", ParentId = 2, DisplayOrder = 1 }, new MenuItem { Id = 5, Title = "製品B", Url = "/products/b", ParentId = 2, DisplayOrder = 2 }, new MenuItem { Id = 6, Title = "サービスX", Url = "/services/x", ParentId = 3, DisplayOrder = 1 }, new MenuItem { Id = 7, Title = "サービスY", Url = "/services/y", ParentId = 3, DisplayOrder = 2 }, new MenuItem { Id = 8, Title = "製品B詳細", Url = "/products/b/detail", ParentId = 5, DisplayOrder = 1 } }; var menuTree = BuildTree(flatMenu); MarkActiveMenu(menuTree, HttpContext.Request.Path); return View(menuTree.OrderBy(m => m.DisplayOrder).ToList()); } private List<MenuItem> BuildTree(List<MenuItem> items) { var lookup = items.ToDictionary(x => x.Id); var roots = new List<MenuItem>(); foreach (var item in items.OrderBy(i => i.DisplayOrder)) { if (item.ParentId.HasValue && lookup.ContainsKey(item.ParentId.Value)) { lookup[item.ParentId.Value].Children.Add(item); lookup[item.ParentId.Value].Children = lookup[item.ParentId.Value].Children.OrderBy(c => c.DisplayOrder).ToList(); } else { roots.Add(item); } } return roots; } private bool MarkActiveMenu(List<MenuItem> menuItems, string currentPath) { bool found = false; foreach (var item in menuItems) { if (item.Url?.Equals(currentPath, System.StringComparison.OrdinalIgnoreCase) == true) { item.IsActive = true; found = true; } else if (item.Children.Any() && MarkActiveMenu(item.Children, currentPath)) { item.IsActive = true; found = true; } } return found; } } }

3. メインビュー(Bootstrap + 多階層CSS + ホバー展開 + アニメーション)

Html
@model List<YourApp.Models.MenuItem> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> .dropdown-submenu { position: relative; } .dropdown-submenu > .dropdown-menu { top: 0; left: 100%; margin-top: -0.125rem; } /* アクティブ強調 */ .nav-link.active, .dropdown-item.active { background-color: #0d6efd; color: #fff !important; } /* ホバー展開 */ .navbar-nav li:hover > .dropdown-menu { display: block; } /* アニメーション */ .dropdown-menu { display: none; opacity: 0; transition: opacity 0.3s ease; } .navbar-nav li:hover > .dropdown-menu { display: block; opacity: 1; } </style> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <ul class="navbar-nav"> @foreach (var item in Model) { @Html.Partial("_MenuItemPartial", item) } </ul> </div> </nav> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

4. 再帰的部分ビュー(アイコン + アクティブ + 並び順)

Html
@model YourApp.Models.MenuItem @if (Model.Children.Any()) { <li class="nav-item dropdown @(Model.ParentId != null ? "dropdown-submenu" : "")"> <a class="nav-link dropdown-toggle @(Model.IsActive ? "active" : "")" href="@Model.Url" role="button" data-bs-toggle="dropdown" aria-expanded="false"> @if (!string.IsNullOrEmpty(Model.IconClass)) { <i class="@Model.IconClass"></i> } @Model.Title </a> <ul class="dropdown-menu"> @foreach (var child in Model.Children.OrderBy(c => c.DisplayOrder)) { @Html.Partial("_MenuItemPartial", child) } </ul> </li> } else { <li> <a class="dropdown-item @(Model.IsActive ? "active" : "")" href="@Model.Url"> @if (!string.IsNullOrEmpty(Model.IconClass)) { <i class="@Model.IconClass"></i> } @Model.Title </a> </li> }

改良点

  1. 表示順制御
    DisplayOrder プロパティでメニューの並び順を管理。
  2. アイコン対応
    Font Awesome を利用して視覚的にわかりやすく。
  3. ホバー展開 + アニメーション
    マウスオーバー時にフェードインするようにCSS

Powered by Froala Editor

添付画像

画像はありません
Genreのカテゴリ-名
Views
編集 | 記事一覧