htmlasp.netrazor-pagesasp.net-core-8

Navigation Dropdowns in ASP.NET Core 8.0 Razor Pages - How to prevent opening new pages?


I am developing an ASP.NET Core 8.0 application using Razor Pages. I have implemented a dynamic navigation menu that needs to be consistently displayed across all pages of my application. The navigation menu items are fetched dynamically based on the user's role from a database.

I have successfully implemented a navigation menu using Razor Pages and I'm currently displaying it on a dedicated Main.cshtml page. However, when a user navigates to another page, the new page opens independently of the navigation menu, which I want to remain consistent across all pages.

Main.cshtml code:

  <ul class="horizontal-list ">
      @foreach (var menuItem in Model.MenuItems.Where(m => m.parentId == null))
      {
          <li>
              <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">@menuItem.formName</a>
              <ul class="dropdown-menu">
                  @foreach (var subMenuItem in Model.MenuItems.Where(m => m.parentId == menuItem.formID))
                  {
                      @if (Model.MenuItems.Any(m => m.parentId == subMenuItem.formID))
                      {
                          <li class="dropdown-submenu">
                              <a class="dropdown-item dropdown-toggle menutext" href="#">@subMenuItem.formName</a>
                              <ul class="dropdown-menu">
                                  @foreach (var subsubMenuItem in Model.MenuItems.Where(m => m.parentId == subMenuItem.formID))
                                  {
                                      <li class="dropdown-menu-1">
                                        <a class="dropdown-item menutext" href="@subsubMenuItem.formUrl">@subsubMenuItem.formName</a>
                                      </li>
                                  }
                              </ul>
                          </li>
                      }
                      else
                      {
                          <li class="dropdown-submenu">
                              <a class="dropdown-item menutext" href="@subMenuItem.formUrl">@subMenuItem.formName</a>
                          </li>
                      }
                  }
              </ul>
          </li>
      }
  </ul>

Main.cshtml.cs code:

public List<Menu> GetMenusForRole(long roleId)
{
    var menuList = new List<Menu>();
    
    // Example logic to fetch menus based on roleId from a database
    ArrayList parameters = new ArrayList { roleId };
    DataTable dtMenus = ExecuteStoredProcedure.ExecProc_getDataTable(_configuration, "Stored_Procedure_Name", parameters);
    
    foreach (DataRow row in dtMenus.Rows)
    {
        menuList.Add(new Menu
        {
            formID = Convert.ToInt64(row["Form_ID"].ToString()),
            formName = row["Form_Title"].ToString(),
            parentId = row["Form_Parent_ID"] == DBNull.Value ? (long?)null : Convert.ToInt64(row["Form_Parent_ID"].ToString()),
            formUrl = row["Form_Url"].ToString()
        });
    }
    
    return menuList;
}

What I've Tried Initially, I created a Main.cshtml and Main.cshtml.cs to display the navigation dropdowns, but this approach leads to the new pages opening independently. I attempted to move the navigation bar into the _Layout.cshtml file, which is used as the main layout template across all pages. However, since the menu items are dynamically fetched based on user roles, I encountered difficulties because _Layout.cshtml does not directly support backend logic.

Desired Outcome: I want to have a navigation menu that remains fixed across all pages of my application, where each menu item is dynamically fetched based on the user's role. When a user navigates to a new page, I want this page to be displayed within the context of the navigation menu, without the menu itself being refreshed.


Solution

  • you could try to place the navigation menu in the _Layout.cshtml file as it does not support backend logic, you will need to make use of a combination of dependency injection and a view component.

    NavigationMenuViewComponent.cs:
    using DynamicMenuProject.Data;
    using DynamicMenuProject.Models;
    using Microsoft.AspNetCore.Mvc;
    
    namespace DynamicMenuProject.Pages.Components
    {
        public class NavigationMenuViewComponent : ViewComponent
        {
            private readonly ApplicationDbContext _context;
    
            public NavigationMenuViewComponent(ApplicationDbContext context)
            {
                _context = context;
            }
    
            public async Task<IViewComponentResult> InvokeAsync()
            {
                var roleId = GetUserRoleId(); // Implement this method to get the current user's role ID
                var menuItems = await Task.Run(() => GetMenusForRole(roleId));
    
                return View(menuItems);
            }
    
            private List<Menu> GetMenusForRole(long roleId)
            {
                return _context.Menus
                               .Where(m => m.Role_Id == roleId)
                               .Select(m => new Menu
                               {
                                   Form_ID = m.Form_ID,
                                   Form_Title = m.Form_Title,
                                   Form_Parent_ID = m.Form_Parent_ID,
                                   Form_Url = m.Form_Url ?? string.Empty, // Provide a default value for NULL URLs
                                   Role_Id = m.Role_Id
                               })
                               .ToList();
            }
    
    
            private long GetUserRoleId()
            {
                // Implement logic to retrieve the current user's role ID
                return 1; // Example role ID
            }
    
        }
    }
    

    Views/Shared/Components/NavigationMenu/Default.cshtml:

    @model List<DynamicMenuProject.Models.Menu>
    
    <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        @foreach (var menuItem in Model.Where(m => m.Form_Parent_ID == null))
        {
            <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown_@menuItem.Form_ID" role="button" data-bs-toggle="dropdown" aria-expanded="false">@menuItem.Form_Title</a>
                <ul class="dropdown-menu" aria-labelledby="navbarDropdown_@menuItem.Form_ID">
                    @foreach (var subMenuItem in Model.Where(m => m.Form_Parent_ID == menuItem.Form_ID))
                    {
                        @if (Model.Any(m => m.Form_Parent_ID == subMenuItem.Form_ID))
                        {
                            <li class="dropdown-submenu">
                                <a class="dropdown-item dropdown-toggle" href="#">@subMenuItem.Form_Title</a>
                                <ul class="dropdown-menu">
                                    @foreach (var subsubMenuItem in Model.Where(m => m.Form_Parent_ID == subMenuItem.Form_ID))
                                    {
                                        <li>
                                            <a class="dropdown-item" href="@subsubMenuItem.Form_Url">@subsubMenuItem.Form_Title</a>
                                        </li>
                                    }
                                </ul>
                            </li>
                        }
                        else
                        {
                            <li>
                                <a class="dropdown-item" href="@subMenuItem.Form_Url">@subMenuItem.Form_Title</a>
                            </li>
                        }
                    }
                </ul>
            </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"] - DynamicMenuProject</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="~/DynamicMenuProject.styles.css" asp-append-version="true" />
    </head>
    <body>
        <header>
            <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
                <div class="container">
                    <a class="navbar-brand" asp-area="" asp-page="/Index">DynamicMenuProject</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">
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                            </li>
                        </ul>
                        <!-- Render the dynamic navigation menu -->
                        @await Component.InvokeAsync("NavigationMenu")
                    </div>
                </div>
            </nav>
        </header>
        <div class="container">
            <main role="main" class="pb-3">
                @RenderBody()
            </main>
        </div>
    
        <footer class="border-top footer text-muted">
            <div class="container">
                &copy; 2024 - DynamicMenuProject - <a asp-area="" asp-page="/Privacy">Privacy</a>
            </div>
        </footer>
    
        <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"></script>
    
        @await RenderSectionAsync("Scripts", required: false)
    </body>
    </html>
    

    enter image description here