Dynamic Menus in ASP.NET Core

By Tan Lee Published on Feb 28, 2025  181
Implementing dynamic menus on the layout page in ASP.NET Core involves several steps.

1. Enable Sessions in ASP.NET Core

Sessions are used to store user-specific data, such as menu items, across requests. To enable sessions, configure them in the Program.cs file.

var builder = WebApplication.CreateBuilder(args);

// Add session services
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // Session timeout
    options.Cookie.HttpOnly = true; // Prevent client-side script access
    options.Cookie.IsEssential = true; // Mark the session cookie as essential
});

var app = builder.Build();

// Add session middleware to the pipeline
app.UseSession();

app.Run();

2. Define the Menu Model

Create a model to represent the menu structure. For example, you can define a Menu class with properties like IdNameAction, and SubMenus.

public class Menu
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Action { get; set; }
    public string Controller { get; set; }
    public List<Menu> SubMenus { get; set; } = new List<Menu>();
}

3. Store Menu Data in Session

After a user logs in or during application startup, fetch the menu data (e.g., from a database or configuration) and store it in the session.

public class AccountController : Controller
{
    [HttpPost]
    public async Task<IActionResult> Login(LoginModel model)
    {
        if (ModelState.IsValid)
        {
            // Authenticate the user
            var user = await _userService.AuthenticateAsync(model.Username, model.Password);

            if (user != null)
            {
                // Fetch menu data based on user role
                var menus = await _menuService.GetMenusByRoleAsync(user.Role);

                // Store menu data in session
                HttpContext.Session.SetString("Menus", JsonConvert.SerializeObject(menus));

                return RedirectToAction("Dashboard", "Home");
            }
            else
            {
                ModelState.AddModelError("", "Invalid username or password.");
            }
        }

        return View(model);
    }
}

4. Retrieve and Display Menu in _Layout.cshtml

In the layout page (_Layout.cshtml), retrieve the menu data from the session and render it dynamically.

Inject IHttpContextAccessor: Inject IHttpContextAccessor to access the session in the view.

@inject IHttpContextAccessor HttpContextAccessor
@using Newtonsoft.Json

Retrieve Menu Data: Deserialize the menu data from the session.

@{
    var menusJson = HttpContextAccessor.HttpContext?.Session.GetString("Menus");
    List<Menu> menus = new List<Menu>();

    if (!string.IsNullOrEmpty(menusJson))
    {
        menus = JsonConvert.DeserializeObject<List<Menu>>(menusJson);
    }
}

Render the Menu: Use a loop to render the menu dynamically.

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">My App</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
                @if (menus != null)
                {
                    @foreach (var menu in menus)
                    {
                        <li class="nav-item">
                            <a class="nav-link" href="@Url.Action(menu.Action, menu.Controller)">@menu.Name</a>
                            @if (menu.SubMenus != null && menu.SubMenus.Any())
                            {
                                <ul class="dropdown-menu">
                                    @foreach (var subMenu in menu.SubMenus)
                                    {
                                        <li><a class="dropdown-item" href="@Url.Action(subMenu.Action, subMenu.Controller)">@subMenu.Name</a></li>
                                    }
                                </ul>
                            }
                        </li>
                    }
                }
            </ul>
        </div>
    </div>
</nav>

Use View Components for Reusability

For better organization, you can encapsulate the menu rendering logic in a View Component.

Create a View Component: Create a MenuViewComponent class.

public class MenuViewComponent : ViewComponent
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MenuViewComponent(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        var menusJson = _httpContextAccessor.HttpContext?.Session.GetString("Menus");
        List<Menu> menus = new List<Menu>();

        if (!string.IsNullOrEmpty(menusJson))
        {
            menus = JsonConvert.DeserializeObject<List<Menu>>(menusJson);
        }

        return View(menus);
    }
}

Create the View Component View: Create a view for the component at Views/Shared/Components/Menu/Default.cshtml.

@model List<Menu>

<ul class="navbar-nav">
    @foreach (var menu in Model)
    {
        <li class="nav-item">
            <a class="nav-link" href="@Url.Action(menu.Action, menu.Controller)">@menu.Name</a>
            @if (menu.SubMenus != null && menu.SubMenus.Any())
            {
                <ul class="dropdown-menu">
                    @foreach (var subMenu in menu.SubMenus)
                    {
                        <li><a class="dropdown-item" href="@Url.Action(subMenu.Action, subMenu.Controller)">@subMenu.Name</a></li>
                    }
                </ul>
            }
        </li>
    }
</ul>

Render the View Component in _Layout.cshtml: Invoke the view component in the layout page.

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">My App</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            @await Component.InvokeAsync("Menu")
        </div>
    </div>
</nav>

By following these steps, you can implement dynamic menus in ASP.NET Core that adapt to user roles and permissions. Using sessions and view components ensures a clean, maintainable, and scalable solution.