54

When I want a specific menu link to be active at a given page, I'm using this approach in Razor:

On the master layout I have these checks:

var active = ViewBag.Active;
const string ACTIVE_CLASS = "current";

if (active == "home")
{
    ViewBag.ActiveHome = ACTIVE_CLASS;
}
if (active == "products")
{
    ViewBag.ActiveProducts = ACTIVE_CLASS;
}

etc.

The html menu on the master layout:

<ul>
<li class="@ViewBag.ActiveHome"><a href="/">Home</a></li>
<li class="@ViewBag.ActiveProducts"><a href="@Url.Action("index", "products")">Products</a></li>
</ul>

When specifying which layout page to use on a different view:

@{
    ViewBag.Active = "home";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

Is there a better approach to sepcify active links, than the one I'm currently using?

Jens
  • 575
  • 1
  • 5
  • 7

6 Answers6

126

A better approach is to use a HTML helper:

using System.Web.Mvc; 
using System.Web.Mvc.Html;

public static class MenuExtensions
{
    public static MvcHtmlString MenuItem(
        this HtmlHelper htmlHelper, 
        string text,
        string action, 
        string controller
    )
    {
        var li = new TagBuilder("li");
        var routeData = htmlHelper.ViewContext.RouteData;
        var currentAction = routeData.GetRequiredString("action");
        var currentController = routeData.GetRequiredString("controller");
        if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
            string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
        {
            li.AddCssClass("active");
        }
        li.InnerHtml = htmlHelper.ActionLink(text, action, controller).ToHtmlString();
        return MvcHtmlString.Create(li.ToString());
    }
}

and then:

<ul>
    @Html.MenuItem("Home", "Home", "Home")
    @Html.MenuItem("Products", "Index", "Products")
</ul>

To make the above work you need your views to recognize your extension: In Web.config in the Views folder, add <add namespace="yourNamespacehere.Helpers" /> inside the namespaces tag. Then build your project and close and re-open the view you are adding this to.

then based on the current action and controller the helper will add or not the active class when generating the anchor.

Peter
  • 14,221
  • 15
  • 70
  • 110
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Looks like those mvc html helpers is a great idea, I might look more into that. Thanks, works like a charm. – Jens Jun 12 '11 at 16:34
  • Nice! I wanted to do this but couldn't figure out how to work out the active controller and action from inside an html helper. – Richard Garside Jun 28 '11 at 20:58
  • 1
    Needed the same -- exactly -- and this did the trick. We must both be using Bootstrap. Thanks! – davidbitton Aug 06 '12 at 13:30
  • Where exactly do you put this custom helper? – Fergal Feb 03 '13 at 14:05
  • Compiler Error Message: CS1061: 'System.Web.Mvc.HtmlHelper' does not contain a definition for 'MenuItem' and no extension method 'MenuItem' accepting a first argument of type 'System.Web.Mvc.HtmlHelper' could be found (are you missing a using directive or an assembly reference?) – Fergal Feb 03 '13 at 14:25
  • 1
    @Fergal, make sure you have brought the namespace in which the extension method is defined into scope in the view. – Darin Dimitrov Feb 03 '13 at 14:48
  • 22
    Thank you, just to clarify for fellow newbies to C# and MVC 3, here is what I had to do: create Helpers folder on root, create a class, MenuExtensions.cs, with the above code in this folder. To the top of the file add `using System.Web.Mvc;` `using System.Web.Mvc.Html;` In Web.config in the Views folder, add `` inside the namespaces tag. – Fergal Feb 03 '13 at 16:18
  • How do you bring the namespace extension method into scope in the view? I have added the class in a Helpers folder and have added the namespace to the webconfig under – Jay Aug 26 '13 at 22:03
  • I am receiving the same error: System.Web.Mvc.HtmlHelper' does not contain a definition for 'MenuItem' and no extension method 'MenuItem' accepting a first argument of type 'System.Web.Mvc.HtmlHelper' could be found (are you missing a using directive or an assembly reference?) – Jay Aug 26 '13 at 22:03
  • newbies to razor, if you are running visual studio in safe mode intellisense won't show this extension method. – Sandeep Polavarapu Nov 02 '13 at 11:00
  • @Fergal, It's not working in partial view as it's showing only menus, active is not enabled. I have dynamic menus which are coming from databse. So i have to use partial view. – KomalJariwala Jan 31 '14 at 11:43
  • I took the liberty to put the pointers that @Fergal noted in the answer for completion sake. – Peter Apr 24 '14 at 14:15
6

Expanding on Darin's example, here's the full class which adds additional optional parameters for RouteValues and HtmlAttributes on the helper. In effect, it behaves just like the base ActionLink.

using System;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace MYNAMESPACE.Helpers {
    public static class MenuExtensions {
        public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper,
                                             string text, string action,
                                             string controller,
                                             object routeValues = null,
                                             object htmlAttributes = null) {
            var li = new TagBuilder("li");
            var routeData = htmlHelper.ViewContext.RouteData;
            var currentAction = routeData.GetRequiredString("action");
            var currentController = routeData.GetRequiredString("controller");
            if (string.Equals(currentAction,
                              action,
                              StringComparison.OrdinalIgnoreCase) &&
                string.Equals(currentController,
                              controller,
                              StringComparison.OrdinalIgnoreCase)) {
                li.AddCssClass("active");
            }
            if (routeValues != null) {
                li.InnerHtml = (htmlAttributes != null)
                    ? htmlHelper.ActionLink(text,
                                            action,
                                            controller,
                                            routeValues,
                                            htmlAttributes).ToHtmlString()
                    : htmlHelper.ActionLink(text, 
                                            action, 
                                            controller, 
                                            routeValues).ToHtmlString();
            }
            else {
                li.InnerHtml = htmlHelper.ActionLink(text, 
                                                     action, 
                                                     controller).ToHtmlString();
            }
            return MvcHtmlString.Create(li.ToString());
        }
    }
}

And in the View folder's web.config:

<system.web.webPages.razor>
  <host ... />
  <pages ... >
    <namespaces>
      ...

      ...
      <add namespace="MYNAMESPACE.Helpers" />
    </namespaces>
  </pages>
</system.web.webPages.razor>
dperish
  • 1,493
  • 16
  • 27
2

Use this InnerHtml if you'd like to include HTML formatting in your text;

li.InnerHtml = "<a href=\"" + new UrlHelper(htmlHelper.ViewContext.RequestContext).Action(action, controller).ToString() + "\">" + text + "</a>";

text could be "<b>Bold</b>Normal";

edocetirwi
  • 542
  • 5
  • 22
2

Updated for RC2 - For those wondering how to do this in MVC6 / Asp.Net 5 - similar but subtly different. There's now no MvcHtmlString, and the RouteData works completely differently. Also, the context object should now be IHtmlContent rather than HtmlHelper.

using System;
using Microsoft.AspNet.Mvc.Rendering;

public static class MenuExtensions
{
    public static IHtmlContent MenuItem(
        this IHtmlHelper htmlHelper,
        string text,
        string action,
        string controller
    )
    {

        var li = new TagBuilder("li") { TagRenderMode = TagRenderMode.Normal };
        var routeData = htmlHelper.ViewContext.RouteData;
        var currentAction = routeData.Values["action"].ToString();
        var currentController = routeData.Values["controller"].ToString();

        if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
            string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
        {
            li.AddCssClass("active");
        }


        li.InnerHtml.AppendHtml(htmlHelper.ActionLink(text, action, controller));

        return li;


    }
}
Party Ark
  • 1,061
  • 9
  • 20
0

This code worked great for me, even on a new Visual Studio 2013 MVC5/Bootstrap project. Note also that you could change the li.AddCssClass("active"); line to point to a custom class if you want to leave the Bootstrap "active" class alone. I added one called "activemenu" in the project's Site.css file and did any specific navbar styling changes I wanted there.

The line in the code above was just changed to this to get it all working:

li.AddCssClass("activemenu");

In Site.css I added a simple class for my purpose:

.activemenu {
    text-decoration: underline;
}

Alternatively you could change the background color and/or border, etc...

ggoodro
  • 1
  • 1
0

here is an extention on Darin's class to insert html in the link text rather than a simple text

using System;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace YourNameSpaceHere
{
    public static class MenuExtensions
    {
        public static MvcHtmlString MenuItem(
            this HtmlHelper htmlHelper,
            string html,
            string action,
            string controller
        )
        {
            var li = new TagBuilder("li");
            var routeData = htmlHelper.ViewContext.RouteData;
            var currentAction = routeData.GetRequiredString("action");
            var currentController = routeData.GetRequiredString("controller");
            if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
            {
                li.AddCssClass("active");
            }
            //generate a unique id for the holder and convert it to string
            string holder = Guid.NewGuid().ToString();
            string anchor = htmlHelper.ActionLink(holder, action, controller).ToHtmlString();
            //replace the holder string with the html
            li.InnerHtml = anchor.Replace(holder, html);
            return MvcHtmlString.Create(li.ToString());
        }
    }
}

and use it like this:

<ul>
    @Html.MenuItem("<span class'ClassName'>Home</span>", "Home", "Home")
</ul>
MAK5
  • 1
  • 1
  • 4