Navigation Menus in ASP.NET Core

Navigation-Menus in ASP.NET Core Application

In this article, I am going to discuss how to create Responsive Navigation Menus in ASP.NET Core Application using bootstrap and JQuery. Please read our previous article, where we discussed the Environment Tag Helper in ASP.NET Core Application.

On a large screen device, we want the navigation menus to look as shown below.

Large Device Navigation Menus in ASP.NET Core Application

On a small screen device, we want to show the navigation menus like below.

Small Device Navigation Menus in ASP.NET Core Application

Adding bootstrap and JQuery files:

The most important point that you need to remember is Bootstrap 4 has a dependency on jQuery. So, here we need to download both bootstraps as well as JQuery into our application. Here, we are going to use a tool called Library Manager (LibMan) to download the required bootstrap and JQuery files. If you are new to Library Manager then I strongly recommended you to read the following article where we discussed how to use LibMan to download client-side libraries.

https://dotnettutorials.net/lesson/how-to-install-bootstrap-in-asp-net-core/

Adding images Folder:

Add a folder called images with the wwwroot folder and then paste two different images with the name Logo.png and Student.png.

Adding css Folder:

Again add a folder with the name css within the wwwroot folder and then add a css file with the name MyCustomStyleSheet.css. Once you create the css file, then copy and paste the following code in it.

.btn {
   width: 80px;
}

With the above files and folders in place, your wwwroot folder should looks as shown below.

Responsive Navigation Menus in ASP.NET Core Application

_ViewImports.cshtml file:

Please modify the _ViewImports.cshtml file as shown below.

@using FirstCoreMVCApplication.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
_Layout.cshtml file:

Please modify the _Layout.cshtml file as shown below.

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/css/MyCustomStyleSheet.css" rel="stylesheet" />
    <link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
    <script src="~/lib/jquery/jquery.js"></script>
    <script src="~/lib/twitter-bootstrap/js/bootstrap.js"></script>
</head>
<body>
    <div class="container">
        <nav class="navbar navbar-expand-sm bg-dark navbar-dark">
            <a class="navbar-brand" asp-controller="home" asp-action="index">
                <img src="~/images/Logo.png" width="30" height="30">
            </a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="collapsibleNavbar">
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="home" asp-action="index">List</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="home" asp-action="create">Create</a>
                    </li>
                    <li>
                        <a class="nav-link" asp-controller="home" asp-action="about">About</a>
                    </li>
                    <li>
                        <a class="nav-link" asp-controller="home" asp-action="contact">Contact</a>
                    </li>
                </ul>
            </div>
        </nav>

        <div>
            @RenderBody()
        </div>
    </div>
</body>
</html>

Note: On a small screen device, for the navbar toggle button to work, the jQuery reference must be loaded before loading the Bootstrap JavaScript file. If you change the order, then the navbar toggle button does not work as expected.

Creating Models:

First, we need to create two enums to store the Gender and Branch of a student. So, create two models within the Models folder with the name Gender and Branch and then copy and paste the following code.

Branch.cs

namespace FirstCoreMVCApplication.Models
{
    public enum Branch
    {
        None,
        CSE,
        ETC,
        Mech
    }
}

Gender.cs

namespace FirstCoreMVCApplication.Models
{
    public enum Gender
    {
        Male,
        Female
    }
}

Please add the following Student model with the Models folder.

Student.cs

using System.Collections.Generic;
namespace FirstCoreMVCApplication.Models
{
    public class Student
    {
        public int StudentId { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public Branch Branch { get; set; }
        public Gender Gender { get; set; }
        public string Address { get; set; }
        public IEnumerable<Gender> AllGenders { set; get; }
    }
}
Modifying the Home Controller:

Please modify the Home Controller as shown below.

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

namespace FirstCoreMVCApplication.Controllers
{
    public class HomeController : Controller
    {
        private List<Student> listStudents = new List<Student>();
        public HomeController()
        {
            listStudents = new List<Student>()
            {
               new Student() { StudentId = 101, Name = "James", Branch = Branch.CSE, Gender = Gender.Male, Address = "A1-2018", Email = "James@g.com" },
               new Student() { StudentId = 102, Name = "Priyanka", Branch = Branch.ETC, Gender = Gender.Female, Address = "A1-2019", Email = "Priyanka@g.com" },
               new Student() { StudentId = 103, Name = "David", Branch = Branch.CSE, Gender = Gender.Male, Address = "A1-2020", Email = "David@g.com" }
            };
        }
        public ViewResult Index()
        {
            return View(listStudents);
        }

        public ViewResult Details(int Id)
        {
            var studentDetails = listStudents.FirstOrDefault(std => std.StudentId == Id);
            return View(studentDetails);
        }
    }
}
Index.cshtml file:

Please modify the Index view as shown below.

@model List<Student>
@{
    ViewBag.Title = "Student List";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="card-deck">
    @foreach (var student in Model)
    {
        <div class="card m-3">
            <div class="card-header">
                <h3>@student.Name</h3>
            </div>
            <img class="card-img-top" src="~/images/Student.png" />
            <div class="card-footer text-center">
                <div class="card-footer text-center">
                    <a asp-controller="home" asp-action="details"
                       asp-route-id="@student.StudentId" class="btn btn-primary m-1">View</a>
                    <a href="#" class="btn btn-primary m-1">Edit</a>
                    <a href="#" class="btn btn-danger m-1">Delete</a>
                </div>
            </div>
        </div>
    }
</div>
Detail.cshtml file:

Please modify the Details view as shown below.

@model Student
@{
    ViewBag.Title = "Student Details";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="row justify-content-center m-3">
    <div class="col-sm-8">
        <div class="card">
            <div class="card-header">
                <h1>@Model.Name</h1>
            </div>

            <div class="card-body text-center">
                <img class="card-img-top" src="~/images/Student.png" />
                <h4>Studnet ID : @Model.StudentId</h4>
                <h4>Email : @Model.Email</h4>
                <h4>Branch : @Model.Branch</h4>
                <h4>Gender : @Model.Gender</h4>
                <h4>Address : @Model.Address</h4>
            </div>
            <div class="card-footer text-center">
                <a href="#" class="btn btn-primary">Back</a>
                <a href="#" class="btn btn-primary">Edit</a>
                <a href="#" class="btn btn-danger">Delete</a>
            </div>
        </div>
    </div>
</div>

Now run the application and see the navigation menus which should be responsive. In the next article, I am going to discuss Form Tag Helpers in ASP.NET Core Application. Here, in this article, I try to explain how to create Responsive Navigation Menus in ASP.NET Core Application using bootstrap with some examples.

3 thoughts on “Navigation Menus in ASP.NET Core”

  1. blank

    Hello,

    i tried this implementing but facing small issue. I followed all steps ui is coming correctly but anchore tag helper not rendering its showing as taghelper.
    i modified removing ; in _ViewImports.cshtml then tag helpers working properly. but UI is not working properly.

    @using AspNetCore_Menu.Models
    @using AspNetCore_Menu.Models.DataTypes
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

    if ; is kept for each like @using AspNetCore_Menu.Models; then ui is properly coming.

    startup.cs
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddMvc(options => options.EnableEndpointRouting = false);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    if (env.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    }
    app.UseStaticFiles();

    app.UseRouting();

    app.UseMvcWithDefaultRoute();
    }

    Any suggestions.

    and thanks for nice article its easy to understand.

  2. blank

    Can you update the page to include the img files you referred to (Logo.png and Student.png)? For adding bootstrap and JQuery files, is it the same as adding the client package of twitter-bootstrap you mentioned earlier? Thanks.

  3. blank

    [Generator]
    public class MenuPagesGenerator : ISourceGenerator
    {
    private const string RouteAttributeName = “Microsoft.AspNetCore.Components.RouteAttribute”;
    private const string DescriptionAttributeName = “System.ComponentModel.Description”;

    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
    try
    {
    var menuComponents = GetMenuComponents(context.Compilation);

    var pageDetailsSource = SourceText.From(Templates.MenuPages(menuComponents), Encoding.UTF8);
    context.AddSource(“PageDetails”, pageDetailsSource);
    context.AddSource(“PageDetail”, SourceText.From(Templates.PageDetail(), Encoding.UTF8));
    }
    catch (Exception)
    {
    Debugger.Launch();
    }
    }

    private static ImmutableArray GetMenuComponents(Compilation compilation)
    {
    // Get all classes
    IEnumerable allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
    IEnumerable allClasses = allNodes
    .Where(d => d.IsKind(SyntaxKind.ClassDeclaration))
    .OfType();

    return allClasses
    .Select(component => TryGetMenuComponent(compilation, component))
    .Where(page => page is not null)
    .Cast()// stops the nullable lies
    .ToImmutableArray();
    }

    private static RouteableComponent? TryGetMenuComponent(Compilation compilation, ClassDeclarationSyntax component)
    {
    var attributes = component.AttributeLists
    .SelectMany(x => x.Attributes)
    .Where(attr =>
    attr.Name.ToString() == RouteAttributeName
    || attr.Name.ToString() == DescriptionAttributeName)
    .ToList();

    var routeAttribute = attributes.FirstOrDefault(attr => attr.Name.ToString() == RouteAttributeName);
    var descriptionAttribute = attributes.FirstOrDefault(attr => attr.Name.ToString() == DescriptionAttributeName);

    if (routeAttribute is null || descriptionAttribute is null)
    {
    return null;
    }

    if (
    routeAttribute.ArgumentList?.Arguments.Count != 1 ||
    descriptionAttribute.ArgumentList?.Arguments.Count != 1)
    {
    // no route path or description value
    return null;
    }

    var semanticModel = compilation.GetSemanticModel(component.SyntaxTree);

    var routeArg = routeAttribute.ArgumentList.Arguments[0];
    var routeExpr = routeArg.Expression;
    var routeTemplate = semanticModel.GetConstantValue(routeExpr).ToString();

    var descriptionArg = descriptionAttribute.ArgumentList.Arguments[0];
    var descriptionExpr = descriptionArg.Expression;
    var title = semanticModel.GetConstantValue(descriptionExpr).ToString();

    return new RouteableComponent(routeTemplate, title);
    }
    }
    _________
    //MENU builder
    [Route(“string”)]
    public IActionResult RenderString()
    {
    return View(“String”,GetMenuString());
    }
    private string GetMenuString()
    {
    var menuItems = GetAllMenuItems();

    var builder = new StringBuilder();
    builder.Append(“”);
    builder.Append(GetMenuLiString(menuItems, null));
    builder.Append(“”);
    return builder.ToString();
    }
    private string GetMenuLiString(IList menuList, string parentId)
    {
    var children = GetChildrenMenu(menuList, parentId);

    if(children.Count<=0)
    {
    return "";
    }

    var builder = new StringBuilder();

    foreach (var item in children)
    {
    var childStr = GetMenuLiString(menuList, item.ID);
    if(!string.IsNullOrWhiteSpace(childStr))
    {
    builder.Append("”);
    builder.Append(““);
    builder.AppendFormat(“ {1}”,item.IconClass,item.Content);
    builder.Append(“”);
    builder.Append(““);
    builder.Append(“”);
    builder.Append(“
    “);
    builder.Append(“”);
    builder.Append(childStr);
    builder.Append(“”);
    builder.Append(“”);
    }
    else
    {
    builder.Append(“”);
    builder.AppendFormat(““,item.Href);
    builder.AppendFormat(“ {1}”,item.IconClass,item.Content);
    builder.Append(“
    “);
    builder.Append(“”);
    }
    }

    return builder.ToString();
    }

Leave a Reply

Your email address will not be published. Required fields are marked *