Create the navigation menu

This page is part of a tutorial, which you should follow sequentially from beginning to end. Go to the first page: Xperience by Kentico overview.

In this step, you’ll:

In the previous step of this tutorial, we implemented functionality that displays content from the database on the live site. Now, we’ll code a dynamic menu that allows your content editors to manage the website’s navigation.

Website navigation helps users find the content they are looking for. Well-structured navigation menus provide an overview of how the website’s content (or its specific segment) is structured. Navigation can also help keep users engaged and lead them to explore website content.

Developers need to specify a section in the content tree where editors will build their navigation. Developers must prepare a dedicated “Navigation item” content type through which editors specify the website navigation structure and the desired order of individual menu items.

Add a view model for the menu items

  1. In Visual Studio, expand the Models folder.

  2. Create a new class named NavigationItemViewModel.cs.

  3. Define the view model properties using the following code:

    
    
     namespace MEDLABClinic.Models
     {    
         public class NavigationItemViewModel
         {
             public string MenuItemText { get; set; }
             public string MenuItemRelativeUrl { get; set; }
         }
     }
    
     
  4. Save your changes.

Add a view component for the navigation menu

Let’s add the site’s navigation menu using an ASP.NET Core view component. The component retrieves the menu data and organizes it into a collection of NavigationItemViewModel that gets passed to its view.

  1. Create a new ViewComponents folder in your project.

  2. Add a new NavigationMenuViewComponent.cs class to the folder.

  3. Replace the default code of the class with the following:

    
    
     using System.Threading.Tasks;
     using System.Collections.Generic;
     using System.Linq;
     using Microsoft.AspNetCore.Mvc;
    
     using CMS.Websites;
     using CMS.ContentEngine;
     using CMS.Websites.Routing;
    
     using Kentico.Content.Web.Mvc.Routing;
    
     using MEDLAB;
     using MEDLABClinic.Models;
    
     namespace MEDLABClinic.ViewComponents
     {
         public class NavigationMenuViewComponent : ViewComponent
         {
             private readonly IContentQueryExecutor executor;
             private readonly IWebPageUrlRetriever urlRetriever;
             private readonly IWebsiteChannelContext websiteChannelContext;
             private readonly IPreferredLanguageRetriever preferredLanguageRetriever;
    
             private const string MENU_PATH = "/Navigation";
    
             public NavigationMenuViewComponent(IContentQueryExecutor executor,
                                                IWebPageUrlRetriever urlRetriever,
                                                IWebsiteChannelContext websiteChannelContext,
                                                IPreferredLanguageRetriever preferredLanguageRetriever)
             {
                 // Initializes instances of required services using dependency injection
                 this.executor = executor;
                 this.urlRetriever = urlRetriever;
                 this.websiteChannelContext = websiteChannelContext;
                 this.preferredLanguageRetriever = preferredLanguageRetriever;
             }
    
             public async Task<IViewComponentResult> InvokeAsync()
             {
                 // Query for getting all navigation item pages under the 'Navigation' folder in the content tree
                 var navigationQueryBuilder = new ContentItemQueryBuilder()
                                 .ForContentType(
                                     contentTypeName: NavigationItem.CONTENT_TYPE_NAME,
                                     configureQuery: config => config
                                         .ForWebsite(
                                             websiteChannelName: "MEDLABClinicPages",
                                             pathMatch: PathMatch.Children(path: MENU_PATH,
                                                                           nestingLevel: 1))
                                         // Orders the items according to their position in the content tree
                                         .OrderBy(nameof(IWebPageFieldsSource.SystemFields.WebPageItemOrder))
                                 // Gets items for the currently selected language variant
                                 ).InLanguage(preferredLanguageRetriever.Get());
    
                 // Executes the navigation item query
                 IEnumerable<NavigationItem> navigationPages = await executor.GetMappedWebPageResult<NavigationItem>(navigationQueryBuilder);
    
                 // Retrieves the URLs of pages linked by the navigation items
                 IDictionary<Guid, WebPageUrl> urls = 
                     await urlRetriever.Retrieve(
                                          webPageItemGuids: 
                                            navigationPages.Select(navPage => navPage.LinkTo.FirstOrDefault().WebPageGuid)
                                                           .ToList()
                                                           .AsReadOnly(),
                                          websiteChannelName: "MEDLABClinicPages",
                                          // Gets the URLs for the currently selected language
                                          languageName: preferredLanguageRetriever.Get(),
                                          // Adjusts the URLs if the menu is viewed in Preview mode
                                          forPreview: websiteChannelContext.IsPreview);
    
                 // Creates a collection of view models for the retrieved navigation items
                 IEnumerable<NavigationItemViewModel> menuItems = navigationPages.Select(navPage => new NavigationItemViewModel
                 {
                     MenuItemRelativeUrl = urls[navPage.LinkTo.First().WebPageGuid].RelativePath,
                     MenuItemText = navPage.NavigationItemName
                 });
    
                 // Passes the view model collection to the view
                 return View("Components/_NavigationMenu.cshtml", menuItems);
             }
         }
     }
    
     
  4. Save your changes.

Create the navigation menu view

A navigation menu is an element that appears on most pages of a website. We’ll define the menu’s output in a partial view that is returned by our view component. The menu can then be added to the site’s main layout (and reused on other pages if necessary).

  1. Under the Views/Shared folder, create a new Components folder.

  2. Within the Components folder, create a new view named _NavigationMenu.cshtml.

  3. Replace the default code of the view with:

    
    
     @using MEDLABClinic.Models;
    
     @model IEnumerable<NavigationItemViewModel>
    
     <nav>
         @foreach (NavigationItemViewModel navigationItem in Model)
         {
             @* Iterates through the view model collection and renders HTML links for the menu items *@
             <a href="@navigationItem.MenuItemRelativeUrl" title="@navigationItem.MenuItemText">@navigationItem.MenuItemText</a>
         }
     </nav>
    
     
  4. Save your changes.

Update the website’s layout

You have coded the functionality behind the navigation menu. Let’s put the code to use and add the menu to the site’s layout.

  1. Edit your _Layout.cshtml file (in the Views/Shared folder).
  2. Locate the section defined by <nav> tags, and replace it with an @await Component.InvokeAsync("NavigationMenu") call. This renders the NavigationMenuViewComponent using the Default.cshtml view we created. Since this is an asynchronous operation, don’t forget to include the await directive.
  3. Locate the <a> element in the <header> section that displays the MEDLAB clinic logo, and set the value of the href attribute to: ~/

Your final _Layout.cshtml markup should look like this:



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    @* Dynamically resolves the page's title *@
    <title>Medlab Clinic - @ViewBag.Title</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css">
    <link href="https://fonts.googleapis.com/css?family=Lato:400,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet" type="text/css">
    <link href="~/css/styles.css" rel="stylesheet" type="text/css" />
    @* Razor section for additional page-specific styles *@
    @RenderSection("styles", required: false)
</head>
<body>
    <header>
        <div class="col-sm-offset-3 col-sm-6">
            <div class="col-sm-6">
                @* Targets the root page of the website *@
                <a href="~/" title="MedlabClinic homepage" class="logo">MEDLAB clinic</a>
            </div>
            <div class="col-sm-6 nav">
                @* Renders the view component with the navigation menu, including the <nav> element *@
                @await Component.InvokeAsync("NavigationMenu")
            </div>
        </div>
        <div class="clearfix"></div>
    </header>
    @* Loads the content of your Tutorial's pages as sub views *@
    @RenderBody()
    <footer>
        <div class="col-sm-offset-3 col-sm-6">
            <div class="row">
                <div class="col-sm-6">
                    <h4>MEDLAB clinic</h4>
                    <ul>
                        <li><i class="fa fa-map-marker"></i> Address: <address>7A Kentico street, Bedford, NH 03110, USA</address></li>
                        <li><i class="fa fa-envelope-o"></i> E-mail: <a href="mailto:info@exampe.com" title="Email us">info@example.com</a></li>
                        <li><i class="fa fa-phone"></i> Phone number: <a href="tel:5417543010" title="Phone us">(541) 754-3010</a>
                    </ul>
                </div>
                <div class="col-sm-6">
                    <span class="cms">Powered by <a href="https://xperience.io/" title="Kentico Xperience CMS">Kentico Xperience CMS for ASP.NET Core</a></span>
                </div>
            </div>
        </div>
        <div class="clearfix"></div>
    </footer>
    @* Razor section for additional page-specific scripts *@
    @RenderSection("scripts", required: false)
</body>
</html>

Rebuild your project after saving the changes and start up the application again (dotnet run in the command line prompt).

Now test your navigation menu on the live site website. Because the original layout already contained hard-coded HTML links, you won’t see a difference. However, you can test the menu’s dynamic functionality by adding another page and navigation item in the Xperience administration interface (the MEDLAB Clinic Pages website channel application), or by changing the order of the Home and Medical center items. The implementation you’ve built ensures any menu item changes are immediately displayed in the navigation menu.

Previous page: Display page content —  Next page: Next steps

Completed pages: 8 of 9