Click here to Skip to main content
13,904,787 members
Click here to Skip to main content
Add your own
alternative version

Stats

3.4K views
9 bookmarked
Posted 11 Dec 2018
Licenced CPOL

Creating a Side Menu for ASP.NET Core using a View Component

, 11 Dec 2018
Rate this:
Please Sign up or sign in to vote.
This article is about creating a dynamic menuing system for ASP.NET core.

While developing our new web application, we wanted to add a menu component that is dynamically generated based on the current route and parameters.

I initially looked into the concept of partials in ASP.NET Core, and while these are great for reusing static markup, they’re not so great for building dynamic, data-driven content such as a dynamic menu.

Where your requirement is to reuse dynamic and / or data driven content, then the correct design approach is to use a ViewComponent. From the Microsoft documentation

After looking at Partial Views, and View Components, I found that ViewComponents don’t have to depend on data already existing. For example, you can simply make an asynchronous call to a server side method like so:

@await Component.InvokeAsync("MenuItems", 1234)

According to the Microsoft documentation, view components are similar to partial views, but they’re much more powerful. View components don’t necessarily use model binding, and only depend on the data provided when called. A view component:

  • Renders a chunk rather than a whole response.
  • Includes the same separation-of-concerns and testability benefits found between a controller and view.
  • Can have parameters and business logic.
  • Is typically invoked from a layout page.

View components are intended anywhere you have reusable rendering logic that’s too complex for a partial view, such as:

  • Dynamic navigation menus
  • Tag cloud (where it queries the database)
  • Login panel

So now, our menu tree structure is handled by a ViewComponent. All the business logic for building a user-specific menu is contained within the ViewComponent, and the ViewComponent returns the menu tree structure. This is then displayed by the Razor Page that calls the ViewComponent. When you call a view component method, you don’t have to pass parameters, and you don’t have to pass a view model. However, with Partials, you need to pass data (a view model), at the time you want to render the Partial view, so you need to have your data ready before hand, making it tightly coupled to your existing view(s).

There are also many other benefits such as:

  • Encapsulate the underlying business logic for a Razor Page in a separate component
  • Allows the business logic to be unit-tested
  • Allow for the UI component to be reused across different forms, essentially acting like an independent view
  • Leads to cleaner code with separation of concerns

Here is the View Component itself:

namespace WebApplication1.Controllers
{
    public class MenuViewComponent : ViewComponent
    {
        private readonly MenuHelper _cbaMenuHelper = new MenuHelper();
     
        public async Task<IViewComponentResult> InvokeAsync(int testId)
        {
            //testId is optional, and can be passed in when you call InvokeAsync
            //Parse current URL to determine which menu item to highlight:
            string baseUrl = Request.Scheme + "://" + Request.Host.Value;
            var httpRequestFeature = Request.HttpContext.Features.Get<IHttpRequestFeature>();
            var uri = httpRequestFeature.RawTarget;
            var id = HttpContext.Request.Query["id"].ToString();  //Parse Query String
            var menuItems = await _cbaMenuHelper.GetAllMenuItems(baseUrl + uri, id); //Can pass in testId here if required.
            return View("_MenuPartial", _cbaMenuHelper.GetMenu(menuItems, null));
        }

    }
}

The ViewComponent calls other methods to actually generate the menu. In this case it’s just a list of Parent -> child mapped menu objects which is included here for your reference:

 public class Menu
    {
        public Guid ID { get; set; }
        public Guid? ParentID { get; set; }
        public string Content { get; set; }
        public string IconClass { get; set; }
        public string Url { get; set; }
        public string SelectedStyle { get; set; }  // If you want a style vs a class;
        public string SelectedClass { get; set; }
        public string OnClick { get; set; }
        public long Order { get; set; }
    }

Once you have your ‘hierarchical’ list of Menu items, just return it to your view as shown inside the InvokeAsync() method. In this case, the view, just recursively displays the menu items according to the parent child relationships within your menu records:

@foreach (var menu in Model)
{
    if (menu.Children.Any())
    {
        <li>
            <a href="#" class="tree-menu-header">
                <i class="@menu.IconClass"></i><span>@menu.Content</span>
            </a>
            <ul class="menu vertical nested is-active">
                <partial name="Components/Menu/_MenuPartial" model="menu.Children" />
            </ul>
        </li>
    }
    else
    {
        <li class="@menu.SelectedClass">
            @{
                if (string.IsNullOrEmpty(menu.Url))   //Add either onclick event or href.
                {
                    <a onclick="@menu.OnClick">
                        <i class="@menu.IconClass"></i><span style="@menu.SelectedStyle">@menu.Content</span>
                    </a>
                }
                else
                {
                    <a href="@menu.Url">
                        <i class="@menu.IconClass"></i><span style="@menu.SelectedStyle;">@menu.Content</span>
                    </a>
                }
            }
        </li>
    }
}

This is returned wrapped inside an instance of IViewComponentResult, which is one of the supported result types returned from a ViewComponent.

This is what the call to invoke the ViewComponent from the layout page looks like:

@await Component.InvokeAsync("Menu", new { cbaId = ViewBag.Id })

I’ve used the ViewBag in this case to pass an ID to the ViewComponent, so there is some context of what to display. You can also see within the ViewComponent that I retrieve the current Route, and use it, along with the Id above to determine what menu items to load.

The end result looks like this:

You can see the ordering is correct, and along with using Font Awesome icons, a nice collapsible menu is created. Clicking on the Create New button runs custom Javascript to most likely create something:

 

The rest of the items are route Url’s, all created from the MenuHelper class. I used the basic ASP.NET core application as a starting point and ‘wrapped’ my menu components around it.

The source code is available for this article at: https://github.com/IntrinsicInnovation/CoreMenuBuilder

I created the entire dynamic menuing system very quickly. And, it’s essentially independent of other views and partial views within your system. This can be reused simply by cutting and pasting all of my code into your application. This is a real world example that solves very real problems with modern application development. Having independent easily testable modules like this ensures your applications are far less likely to cause issues in a production environment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Chris A. Johnson
CEO Kolaberate Software inc.
Canada Canada
Chris is a .NET Architect with over 17 years of experience working with various Microsoft technologies including most recently ASP.NET, SSRS, SSIS, SSAS and Sharepoint. Chris is now C.E.O. of Kolaberate Software, based in Vancouver, B.C.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionGreat Article, Thanks For Sharing Pin
Hyland Computer Systems12-Dec-18 10:51
memberHyland Computer Systems12-Dec-18 10:51 
AnswerRe: Great Article, Thanks For Sharing Pin
Chris A. Johnson7-Jan-19 11:51
professionalChris A. Johnson7-Jan-19 11:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190306.1 | Last Updated 11 Dec 2018
Article Copyright 2018 by Chris A. Johnson
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid