I need to highlight active link in the menu. My menu is in the master page by the way. I'm looking for the best way to implement this? Any ideas?
The best way to handle this is to write an HTML helper. You could use RouteData.Values["action"] to get the currently executing action and compare to the menu name and if they match apply a CSS class that will highlight it.
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string action,
string text
)
{
var menu = new TagBuilder("div");
var currentAction = (string)htmlHelper.ViewContext.RouteData.Values["action"];
if (string.Equals(
currentAction,
action,
StringComparison.CurrentCultureIgnoreCase)
)
{
menu.AddCssClass("highlight");
}
menu.SetInnerText(text);
return MvcHtmlString.Create(menu.ToString());
}
And then use this helper to render the menu items:
<%: Html.MenuItem("about", "About us") %>
<%: Html.MenuItem("contact", "Contact us") %>
...
I am always using this solution
Html Part
<ul>
#Html.ListItemAction("Home Page","Index","Home")
#Html.ListItemAction("Manage","Index","Home")
</ul>
Helper Part
public static class ActiveLinkHelper
{
public static MvcHtmlString ListItemAction(this HtmlHelper helper, string name, string actionName, string controllerName)
{
var currentControllerName = (string)helper.ViewContext.RouteData.Values["controller"];
var currentActionName = (string)helper.ViewContext.RouteData.Values["action"];
var sb = new StringBuilder();
sb.AppendFormat("<li{0}", (currentControllerName.Equals(controllerName, StringComparison.CurrentCultureIgnoreCase) &&
currentActionName.Equals(actionName, StringComparison.CurrentCultureIgnoreCase)
? " class=\"active\">" : ">"));
var url = new UrlHelper(HttpContext.Current.Request.RequestContext);
sb.AppendFormat("{1}", url.Action(actionName, controllerName), name);
sb.Append("</li>");
return new MvcHtmlString(sb.ToString());
}
}
I use this solution:
First - add attribute data-menuPageId to your menu links
<ul class="menu">
<li data-menuPageId="home">
#(Html.Link<HomeController>(x => x.Index(), "Home"))
</li>
<li data-menuPageId="products">
#(Html.Link<ProductsController>(x => x.Index(), "Products"))
</li>
</li>
Next - add hidden field for current page Id to Layout.cshtml
<input type="hidden" id="currentPageId" value="#(ViewBag.Page ?? "home")" />
Add ViewBag.Page initializtion at your pages like Home or Products
#{
ViewBag.Page = "products";
}
And last thing you should reference this javascipt from yor Layout.cshtml
$(function() {
var pageIdAttr = "data-menuPageId";
var currentPage = $("#currentPageId").attr("value");
var menu = $(".menu");
$("a[" + pageIdAttr + "]").removeClass("selected");
$("a[" + pageIdAttr + "=\"" + currentPage + "\"]", menu).addClass("selected");
});
In layout try like following:
#{
string url = Request.RawUrl;
}
#switch (url)
{
case "/home/":
#Html.ActionLink("Home", "../home/", null, new { #style = "background:#00bff3;" })
#Html.ActionLink("Members", "../home/MembersList/")
break;
case "/home/MembersList/":
#Html.ActionLink("Home", "../home/")
#Html.ActionLink("Members", "../home/MembersList/", null, new { #style = "background:#00bff3;" })
break;
default:
#Html.ActionLink("Home", "../home/")
#Html.ActionLink("Members", "../home/MembersList/")
break;
}
The simplest solution:
1) Connect jQuery to #RenderBody ()
2) On Layout
...
<li class="nav-item">
<a class="nav-link text-dark" id="navItemPortfolio" asp-area="" asp-controller="Home" asp-action="Portfolio">Portfolio</a>
</li>
...
3) Write something similar on your page (View)
<script>
$(function () {
$("#navItemPortfolio").addClass("active");
});
</script>
I'm pretty sure you can do this with pure CSS. It involves class selectors and faffing around with the body tag. I would go with the helper method every day of the week.
I'm trying to add an"active" class to my bootstrap navbar in MVC, but the following doesn't show the active class when written like this:
<ul class="nav navbar-nav">
<li>#Html.ActionLink("Home", "Index", "Home", null, new {#class="active"})</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
This resolves to what looks like a correctly formatted class, but doesn't work:
<a class="active" href="/">Home</a>
In the Bootstrap documentation it states that 'a' tags shouldn't be used in the navbar, but the above is how I believe is the correct way of adding a class to an Html.ActionLink. Is there another (tidy) way I can do this?
In Bootstrap the active class needs to be applied to the <li> element and not the <a>. See the first example here: http://getbootstrap.com/components/#navbar
The way you handle your UI style based on what is active or not has nothing to do with ASP.NET MVC's ActionLink helper. This is the proper solution to follow how the Bootstrap framework was built.
<ul class="nav navbar-nav">
<li class="active">#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
Edit:
Since you will most likely be reusing your menu on multiple pages, it would be smart to have a way to apply that selected class automatically based on the current page rather than copy the menu multiple times and do it manually.
The easiest way is to simply use the values contained in ViewContext.RouteData, namely the Action and Controller values. We could build on what you currently have with something like this:
<ul class="nav navbar-nav">
<li class="#(ViewContext.RouteData.Values["Action"].ToString() == "Index" ? "active" : "")">#Html.ActionLink("Home", "Index", "Home")</li>
<li class="#(ViewContext.RouteData.Values["Action"].ToString() == "About" ? "active" : "")">#Html.ActionLink("About", "About", "Home")</li>
<li class="#(ViewContext.RouteData.Values["Action"].ToString() == "Contact" ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
It's not pretty in code, but it'll get the job done and allow you to extract your menu into a partial view if you like. There are ways to do this in a much cleaner way, but since you're just getting started I'll leave it at that. Best of luck learning ASP.NET MVC!
Late edit:
This question seems to be getting a bit of traffic so I figured I'd throw in a more elegant solution using an HtmlHelper extension.
Edit 03-24-2015: Had to rewrite this method to allow for multiple actions and controllers triggering the selected behavior, as well as handling for when the method is called from a child action partial view, thought I'd share the update!
public static string IsSelected(this HtmlHelper html, string controllers = "", string actions = "", string cssClass = "selected")
{
ViewContext viewContext = html.ViewContext;
bool isChildAction = viewContext.Controller.ControllerContext.IsChildAction;
if (isChildAction)
viewContext = html.ViewContext.ParentActionViewContext;
RouteValueDictionary routeValues = viewContext.RouteData.Values;
string currentAction = routeValues["action"].ToString();
string currentController = routeValues["controller"].ToString();
if (String.IsNullOrEmpty(actions))
actions = currentAction;
if (String.IsNullOrEmpty(controllers))
controllers = currentController;
string[] acceptedActions = actions.Trim().Split(',').Distinct().ToArray();
string[] acceptedControllers = controllers.Trim().Split(',').Distinct().ToArray();
return acceptedActions.Contains(currentAction) && acceptedControllers.Contains(currentController) ?
cssClass : String.Empty;
}
Works with .NET Core:
public static string IsSelected(this IHtmlHelper htmlHelper, string controllers, string actions, string cssClass = "selected")
{
string currentAction = htmlHelper.ViewContext.RouteData.Values["action"] as string;
string currentController = htmlHelper.ViewContext.RouteData.Values["controller"] as string;
IEnumerable<string> acceptedActions = (actions ?? currentAction).Split(',');
IEnumerable<string> acceptedControllers = (controllers ?? currentController).Split(',');
return acceptedActions.Contains(currentAction) && acceptedControllers.Contains(currentController) ?
cssClass : String.Empty;
}
Sample usage:
<ul>
<li class="#Html.IsSelected(actions: "Home", controllers: "Default")">
Home
</li>
<li class="#Html.IsSelected(actions: "List,Detail", controllers: "Default")">
List
</li>
</ul>
Extension:
public static MvcHtmlString LiActionLink(this HtmlHelper html, string text, string action, string controller)
{
var context = html.ViewContext;
if (context.Controller.ControllerContext.IsChildAction)
context = html.ViewContext.ParentActionViewContext;
var routeValues = context.RouteData.Values;
var currentAction = routeValues["action"].ToString();
var currentController = routeValues["controller"].ToString();
var str = String.Format("<li role=\"presentation\"{0}>{1}</li>",
currentAction.Equals(action, StringComparison.InvariantCulture) &&
currentController.Equals(controller, StringComparison.InvariantCulture) ?
" class=\"active\"" :
String.Empty, html.ActionLink(text, action, controller).ToHtmlString()
);
return new MvcHtmlString(str);
}
Usage:
<ul class="nav navbar-nav">
#Html.LiActionLink("About", "About", "Home")
#Html.LiActionLink("Contact", "Contact", "Home")
</ul>
I manged to do this by adding a view bag parameter in asp.net mvc. Here what have i done
Added ViewBag.Current = "Scheduler"; like parameter in each page
In layout page
<ul class="nav navbar-nav">
<li class="#(ViewBag.Current == "Scheduler" ? "active" : "") ">Scheduler</li>
</ul>
This solved my problem.
May be little late. But hope this helps.
public static class Utilities
{
public static string IsActive(this HtmlHelper html,
string control,
string action)
{
var routeData = html.ViewContext.RouteData;
var routeAction = (string)routeData.Values["action"];
var routeControl = (string)routeData.Values["controller"];
// both must match
var returnActive = control == routeControl &&
action == routeAction;
return returnActive ? "active" : "";
}
}
And usage as follow:
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class='#Html.IsActive("Home", "Index")'>
#Html.ActionLink("Home", "Index", "Home")
</li>
<li class='#Html.IsActive("Home", "About")'>
#Html.ActionLink("About", "About", "Home")
</li>
<li class='#Html.IsActive("Home", "Contact")'>
#Html.ActionLink("Contact", "Contact", "Home")
</li>
</ul>
</div>
Got reference from http://www.codingeverything.com/2014/05/mvcbootstrapactivenavbar.html
Easy ASP.NET Core 3.0 and TagHelpers
[HtmlTargetElement("li", Attributes = "active-when")]
public class LiTagHelper : TagHelper
{
public string ActiveWhen { get; set; }
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContextData { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (ActiveWhen == null)
return;
var targetController = ActiveWhen.Split("/")[1];
var targetAction = ActiveWhen.Split("/")[2];
var currentController = ViewContextData.RouteData.Values["controller"].ToString();
var currentAction = ViewContextData.RouteData.Values["action"].ToString();
if (currentController.Equals(targetController) && currentAction.Equals(targetAction))
{
if (output.Attributes.ContainsName("class"))
{
output.Attributes.SetAttribute("class", $"{output.Attributes["class"].Value} active");
}
else
{
output.Attributes.SetAttribute("class", "active");
}
}
}
}
Include into your _ViewImports.cs:
#addTagHelper *, YourAssemblyName
Usage:
<li active-when="/Home/Index">
ASP.NET Core & Bootstrap 4
Most up-to-date answer
I have re-worked #crush's neat solution for an updated, ASP.NET Core and Bootstrap 4 compatible way to solve this problem based on an IHtmlHelper extension method:
public static class LinkExtensions
{
public static IHtmlContent ActiveActionLink(this IHtmlHelper html, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
{
return ActiveActionLink(html, linkText, actionName, controllerName, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static IHtmlContent ActiveActionLink(this IHtmlHelper html, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
{
var routeData = html.ViewContext.RouteData;
var routeAction = (string)routeData.Values["action"];
var routeController = (string)routeData.Values["controller"];
var active = controllerName.Equals(routeController) && actionName.Equals(routeAction);
using (var writer = new StringWriter())
{
writer.WriteLine($"<li class='nav-item {(active ? "active" : "")}'>");
html.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes).WriteTo(writer, HtmlEncoder.Default);
writer.WriteLine("</li>");
return new HtmlString(writer.ToString());
}
}
}
Usage
<nav class="navbar">
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
#Html.ActiveActionLink("Home", "Index", "Home", null, new { #class = "nav-link" })
#Html.ActiveActionLink("About", "About", "Home", null, new { #class = "nav-link" })
#Html.ActiveActionLink("Contact", "Contact", "TimeTracking", null, new { #class = "nav-link" })
</ul>
</div>
</nav>
I know this question is old, but I will just like to add my voice here. I believe it is a good idea to leave the knowledge of whether or not a link is active to the controller of the view.
I would just set a unique value for each view in the controller action. For instance, if I wanted to make the home page link active, I would do something like this:
public ActionResult Index()
{
ViewBag.Title = "Home";
ViewBag.Home = "class = active";
return View();
}
Then in my view, I will write something like this:
<li #ViewBag.Home>#Html.ActionLink("Home", "Index", "Home", null, new { title = "Go home" })</li>
When you navigate to a different page, say Programs, ViewBag.Home does not exist (instead ViewBag.Programs does); therefore, nothing is rendered, not even class="". I think this is cleaner both for maintainability and cleanliness. I tend to always want to leave logic out of the view as much as I can.
Considering what Damith posted, I like to think you can just qualify active by the Viewbag.Title (best practice is to populate this in your content pages allowing your _Layout.cshtml page to hold your link bars). Also note that if you are using sub-menu items it also works fine:
<li class="has-sub #(ViewBag.Title == "Dashboard 1" || ViewBag.Title == "Dashboard 2" ? "active" : "" )">
<a href="javascript:;">
<b class="caret"></b>
<i class="fa fa-th-large"></i>
<span>Dashboard</span>
</a>
<ul class="sub-menu">
<li class="#(ViewBag.Title == "Dashboard 1" ? "active" : "")">Dashboard v1</li>
<li class="#(ViewBag.Title == "Dashboard 2" ? "active" : "")">Dashboard v2</li>
</ul>
</li>
This solution is simple for Asp.net MCV 5.
Create a static class, for example Utilitarios.cs.
Inside the Class create a static method:
public static string IsLinkActive(this UrlHelper url, string action, string controller)
{
if (url.RequestContext.RouteData.Values["controller"].ToString() == controller &&
url.RequestContext.RouteData.Values["action"].ToString() == action)
{
return "active";
}
return "";
}
call like this
<ul class="sidebar-menu" data-widget="tree">
<li class="header">HEADER</li>
<li class="#Url.IsLinkActive("Index", "Home")">
<i class="fa fa-link"></i> <span>Home</span>
</li>
<li class="#Url.IsLinkActive("About", "Home")">
<i class="fa fa-link"></i><span>About</span>
</li>
</ul>
I created an HtmlHelper extension that adds an ActiveActionLink method for those of you who want to add the "active" class to the link itself rather than the <li> surrounding the link.
public static class LinkExtensions
{
public static MvcHtmlString ActiveActionLink(this HtmlHelper html, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
{
return ActiveActionLink(html, linkText, actionName, controllerName, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString ActiveActionLink(this HtmlHelper html, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
{
const string activeClassName = "active";
var routeData = html.ViewContext.RouteData;
var routeAction = (string)routeData.Values["action"];
var routeController = (string)routeData.Values["controller"];
var active = controllerName.Equals(routeController) && actionName.Equals(routeAction);
if (active)
{
var #class = (string)htmlAttributes["class"];
htmlAttributes["class"] = string.IsNullOrEmpty(#class)
? activeClassName
: #class + " active";
}
var link = html.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
return link;
}
}
Usage is as follows:
#Html.ActiveActionLink("Home", "Index", "Home", new { area = "" }, new { #class = "nav-item nav-link" })
Most of the solutions on this question all require you to specify the 'page' on both the li and the a (via HtmlHelper) tag. Both #Wolles and #Crush's answers eliminated this duplication, which was nice, but they were using HtmlHelper extension methods instead of TagHelpers. And I wanted to use a TagHelper and support Razor Pages.
You can read my full post here (and get the source code), but the gist of it is:
<bs-menu-link asp-page="/Events/Index" menu-text="Events"></bs-menu-link>
Would render (if the link was active obviously):
<li class="active">Events</li>
My TagHelper leverages the AnchorTagHelper internally (thus supporting asp-page, asp-controller, asp-action, etc. attributes). The 'checks' for active or not is similar to many of the answers to this post.
I modified dom's "not pretty" answer and made it uglier. Sometimes two controllers have the conflicting action names (i.e. Index) so I do this:
<ul class="nav navbar-nav">
<li class="#(ViewContext.RouteData.Values["Controller"].ToString() + ViewContext.RouteData.Values["Action"].ToString() == "HomeIndex" ? "active" : "")">#Html.ActionLink("Home", "Index", "Home")</li>
<li class="#(ViewContext.RouteData.Values["Controller"].ToString() + ViewContext.RouteData.Values["Action"].ToString() == "AboutIndex" ? "active" : "")">#Html.ActionLink("About", "Index", "About")</li>
<li class="#(ViewContext.RouteData.Values["Controller"].ToString() + ViewContext.RouteData.Values["Action"].ToString() == "ContactHome" ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
You can try this:
In my case i am loading menu from database based on role based access, Write the code on your every view which menu your want to active based on your view.
<script type="text/javascript">
$(document).ready(function () {
$('li.active active-menu').removeClass('active active-menu');
$('a[href="/MgtCustomer/Index"]').closest('li').addClass('active active-menu');
});
</script>
is possible with a lambda function
#{
string controllerAction = ViewContext.RouteData.Values["Controller"].ToString() + ViewContext.RouteData.Values["Action"].ToString();
Func<string, string> IsSelected= x => x==controllerAction ? "active" : "";
}
then usage
#Html.ActionLink("Inicio", "Index", "Home", new { area = "" }, new { #class = IsSelected("HomeIndex")})
Add '.ToString' to improve comparing on ASP.NET MVC
<ul class="nav navbar-nav">
<li class="#(ViewContext.RouteData.Values["Action"].ToString() == "Index" ? "active" : "")">#Html.ActionLink("Home", "Index", "Home")</li>
<li class="#(ViewContext.RouteData.Values["Action"].ToString() == "About" ? "active" : "")">#Html.ActionLink("About", "About", "Home")</li>
<li class="#(ViewContext.RouteData.Values["Action"].ToString() == "Contact" ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
--
We also can create UrlHelper from RequestContext which we can get from MvcHandler itself. Therefore I beleive for someone who wants to keep this logic in Razor templates following way would be helpful:
In project root create a folder named AppCode.
Create a file there named HtmlHelpers.cshtml
Create a helper in there:
#helper MenuItem(string action, string controller)
{
var mvcHandler = Context.CurrentHandler as MvcHandler;
if (mvcHandler != null)
{
var url = new UrlHelper(mvcHandler.RequestContext);
var routeData = mvcHandler.RequestContext.RouteData;
var currentAction = routeData.Values["action"].ToString();
var currentController = routeData.Values["controller"].ToString();
var isCurrent = string.Equals(currentAction, action, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(currentController, controller, StringComparison.InvariantCultureIgnoreCase);
<div class="#(isCurrent ? "active" : "")">
<div>#url.Action(action, controller)</div>
</div>
}
}
Then we can use on our views like this:
#HtmlHelpers.MenuItem("Default", "Home")
Hope that it helps to someone.
I would like to propose this solution which is based on the first part of Dom's answer.
We first define two variables, "action" and "controller" and use them to determine the active link:
{ string controller = ViewContext.RouteData.Values["Controller"].ToString();
string action = ViewContext.RouteData.Values["Action"].ToString();}
And then:
<ul class="nav navbar-nav">
<li class="#((controller == "Home" && action == "Index") ? "active" : "")">#Html.ActionLink("Home", "Index", "Home")</li>
<li class="#((controller == "Home" && action == "About") ? "active" : "")">#Html.ActionLink("About", "About", "Home")</li>
<li class="#((controller == "Home" && action == "Contact") ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
Now it looks nicer and no need for more complex solutions.
the answer by #dombenoit works. Though it introduces some code to maintain. Check this syntax out:
using (var nav = Html.Bootstrap().Begin(new Nav().Style(NavType.NavBar).SetLinksActiveByControllerAndAction()))
{
#nav.ActionLink("Link 1", "action1")
#nav.ActionLink("Link 2", "action2")
#nav.Link("External Link", "#")
}
Notice the use of .SetLinksActiveByControllerAndAction() method.
If you wonder what makes this syntax possible, check out TwitterBootstrapMVC
I also was looking for a solution and jQuery helped pretty much. First, you need to give 'id's to your <li> elements.
<li id="listguides"><a href='/Guides/List'>List Guides</a></li>
After doing this in your layout page, you can tell jQuery which <li> element should be 'selected' in your view.
#section Scripts{
<script type="text/javascript">
$('#listguides').addClass('selected');
</script>
}
Note: You need to have #RenderSection("scripts", required: false) in your layout page, just before the </body> tag to add that section.
I realized that this problem was a common problem for some of us, so I published my own solution using nuget package. Below you can see how it works. I hope that will be useful.
Note:This nuget package is my first package. So if you see a mistake, please give feedback. Thank you.
Install Package or download source code and add your Project
-Install-Package Betalgo.MvcMenuNavigator
Add your pages to an enum
public enum HeaderTop
{
Dashboard,
Product
}
public enum HeaderSub
{
Index
}
Put Filter to top of your Controllor or Action
[MenuNavigator(HeaderTop.Product, HeaderSub.Index)]
public class ProductsController : Controller
{
public async Task<ActionResult> Index()
{
return View();
}
[MenuNavigator(HeaderTop.Dashboard, HeaderSub.Index)]
public async Task<ActionResult> Dashboard()
{
return View();
}
}
And use it In your header layout like this
#{
var headerTop = (HeaderTop?)MenuNavigatorPageDataNavigatorPageData.HeaderTop;
var headerSub = (HeaderSub?)MenuNavigatorPageDataNavigatorPageData.HeaderSub;
}
<div class="nav-collapse collapse navbar-collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li class="#(headerTop==HeaderTop.Dashboard?"active selected open":"")">
Dashboard
</li>
<li class="#(headerTop==HeaderTop.Product?"active selected open":"")">
Products
</li>
</ul>
More Info: https://github.com/betalgo/MvcMenuNavigator
I believe here is a cleaner and smalller code to do get the selected menu being "active":
<ul class="navbar-nav mr-auto">
<li class="nav-item #Html.IfSelected("Index")">
<a class="nav-link" href="#Url.Action("Index", "Home")">Home</a>
</li>
<li class="nav-item #Html.IfSelected("Controls")">
<a class="nav-link" href="#Url.Action("Controls", "Home")">MVC Controls</a>
</li>
<li class="nav-item #Html.IfSelected("About")">
<a class="nav-link" href="#Url.Action("About", "Home")">About</a>
</li>
</ul>
public static string IfSelected(this HtmlHelper html, string action)
{
return html
.ViewContext
.RouteData
.Values["action"]
.ToString() == action
? " active"
: "";
}
I found a solution for multiple routes.
TagHelper:
[HtmlTargetElement("li", Attributes = "active-when")]
public class ActiveTagHelper : TagHelper
{
[HtmlAttributeName("active-when")]
public string ActiveWhen { get; set; }
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (ActiveWhen == null)
return;
var hrefs = ActiveWhen.Split(",");
var currentController = ViewContext.RouteData.Values["controller"].ToString();
var currentAction = ViewContext.RouteData.Values["action"].ToString();
foreach (var item in hrefs)
{
var targetController = item.Split("/")[1];
var targetAction = item.Split("/")[2];
if (currentController.Equals(targetController) && currentAction.Equals(targetAction))
{
if (output.Attributes.ContainsName("class"))
{
output.Attributes.SetAttribute("class", $"{output.Attributes["class"].Value} active");
}
else
{
output.Attributes.SetAttribute("class", "active");
}
}
}
}
}
Import:
#addTagHelper *, YourProjectName
Usage:
<li class="nav-item" active-when="/Product/Index,/Product/Add"></li>
At top of the layout page,
#{ var _path = Context.Request.Path.ToString();}
and in your navbar,
<a class="nav-link dropdown-toggle #(_path.Contains("UTBulkFlights") ? "active" : "")">abcLink</a>
if is it is not showing at all, the reason is that you need two # sign:
##class
BUT, I believe you might need to have the active class on the "li" tag not on the "a" tag. according too bootstrap docs (http://getbootstrap.com/components/#navbar-default):
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>Profile</li>
<li>Messages</li>
</ul>
therefore your code will be:
<ul class="nav navbar-nav">
<li class="active">#Html.ActionLink("Home", "Index", "Home", null)</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
I have make combination of answers above and made my solution.
So..
First in razor block create one string variable which will contain name value of controller and action that is called by user.
#{
string controllerAction = ViewContext.RouteData.Values["Controller"].ToString() + ViewContext.RouteData.Values["Action"].ToString();
}
Then use combination of HTML and Razor code:
<ul class="nav navbar-nav">
<li class="#(controllerAction == "HomeIndex" ? "active" : "" )">#Html.ActionLink("Home", "Index", "Home")</li>
<li class="#(controllerAction == "AboutIndex" ? "active" : "" )">#Html.ActionLink("About", "Index", "About")</li>
<li class="#(controllerAction == "HomeContact" ? "active" : "" )">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
I think, that this is good because you don't need to access "ViewContext.RouteData.Values" each time to get controller name and action name.
My Solution to this problem is
<li class="#(Context.Request.Path.Value.ToLower().Contains("about") ? "active " : "" ) nav-item">
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
A better way may be adding an Html extension method to return the current path to be compared with link
I did this:
Made a helper in menu partial view
#helper RouterLink(string action, string controller)
{
var IsActive = ViewContext.RouteData.Values["Controller"].ToString() == controller && ViewContext.RouteData.Values["Action"].ToString() == action;
<text>href="#Url.Action(action, controller)"</text>if (IsActive){ <text>class="active"</text>}
}
Then used it in the anchor tag like this:
<li><a #RouterLink("Index","Home")>Home</a></li>
My application had no areas but it can also be included as another variable in the helper function. And I had to pass the active class to the anchor tag in my view. But li can also be configured like this.
ASP.NET Core & Bootstrap 4 & AdminLte
[HtmlTargetElement("a", Attributes = "active-then")]
[HtmlTargetElement("a", Attributes = "asp-action")]
[HtmlTargetElement("a", Attributes = "asp-controller")]
public class AnchorActiveTagHelper : AnchorTagHelper
{
public AnchorActiveTagHelper(IHtmlGenerator generator) : base(generator)
{
}
[HtmlAttributeName("active-then")]
public string ActiveWhen { get; set; }
[ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContextData { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
if (ActiveWhen == null)
return;
var currentController = ViewContextData.RouteData.Values["controller"].ToString();
var currentAction = ViewContextData.RouteData.Values["action"].ToString();
if (currentController.Equals(Controller) && currentAction.Equals(Action))
{
if (output.Attributes.ContainsName("class"))
{
output.Attributes.SetAttribute("class", $"{output.Attributes["class"].Value} active");
}
else
{
output.Attributes.SetAttribute("class", "active");
}
}
}
}
And
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper *, YourProject.PlatformExtensions
Usage
<li class="nav-item">
<a asp-controller="Home" asp-action="Index" class="nav-link" active-then="active">
<i class="nav-icon fas fa-home"></i>
<p>Home</p>
</a>
</li>
Thanks #WoIIe and #Shahab
As a base for discussion. Create a standard ASP.NET MVC Web project.
It will contain two menu items in the master page:
<div id="menucontainer">
<ul id="menu">
<li>
<%= Html.ActionLink("Home", "Index", "Home")%></li>
<li>
<%= Html.ActionLink("About", "About", "Home")%></li>
</ul>
</div>
How can I set the visual CSS style indicating the current page.
For example, when in the About page/controller, I essentially would like to do this:
<%= Html.ActionLink("About", "About", "Home", new {class="current"})%></li>
And, of course, when on the home page:
<%= Html.ActionLink("Home", "Index", "Home", new {class="current"})%></li>
(Having a CSS style names current that visually indicates in the menu that this is the current page.)
I could break out the menu div from the master page into a content place holder, but that would mean that I must put the menu on every page.
Any ideas, is there a nice solution to this?
The easiest way is to get the current controller and action from the ViewContext's RouteData. Note the change in signature and use of # to escape the keyword.
<% var controller = ViewContext.RouteData.Values["controller"] as string ?? "Home";
var action = ViewContext.RouteData.Values["action"] as string ?? "Index";
var page = (controller + ":" + action).ToLower();
%>
<%= Html.ActionLink( "About", "About", "Home", null,
new { #class = page == "home:about" ? "current" : "" ) %>
<%= Html.ActionLink( "Home", "Index", "Home", null,
new { #class = page == "home:index" ? "current" : "" ) %>
Note that you could combine this an HtmlHelper extension like #Jon's and make it cleaner.
<%= Html.MenuLink( "About", "About", "Home", null, null, "current" ) %>
Where MenuActionLink is
public static class MenuHelperExtensions
{
public static string MenuLink( this HtmlHelper helper,
string text,
string action,
string controller,
object routeValues,
object htmlAttributes,
string currentClass )
{
RouteValueDictionary attributes = new RouteValueDictionary( htmlAttributes );
string currentController = helper.ViewContext.RouteData.Values["controller"] as string ?? "home";
string currentAction = helper.ViewContext.RouteData.Values["action"] as string ?? "index";
string page = string.Format( "{0}:{1}", currentController, currentAction ).ToLower();
string thisPage = string.Format( "{0}:{1}", controller, action ).ToLower();
attributes["class"] = (page == thisPage) ? currentClass : "";
return helper.ActionLink( text, action, controller, new RouteValueDictionary( routeValues ), attributes );
}
}
I recently created an HTML Helper for this that looks like:
public static string NavigationLink(this HtmlHelper helper, string path, string text)
{
string cssClass = String.Empty;
if (HttpContext.Current.Request.Path.IndexOf(path) != -1)
{
cssClass = "class = 'selected'";
}
return String.Format(#"<li><a href='{0}' {1}>{2}</a></li>", path, cssClass, text);
}
The Implementation looks like this:
<ul id="Navigation">
<%=Html.NavigationLink("/Path1", "Text1")%>
<%=Html.NavigationLink("/Path2", "Text2")%>
<%=Html.NavigationLink("/Path3", "Text3")%>
<%=Html.NavigationLink("/Path4", "Text4")%>
</ul>
If you are using T4MVC, you can use this:
public static HtmlString MenuLink(
this HtmlHelper helper,
string text,
IT4MVCActionResult action,
object htmlAttributes = null)
{
var currentController = helper.ViewContext.RouteData.Values["controller"] as string ?? "home";
var currentAction = helper.ViewContext.RouteData.Values["action"] as string ?? "index";
var attributes = new RouteValueDictionary(htmlAttributes);
var cssClass = (attributes.ContainsKey("class"))
? attributes["class"] + " "
: string.Empty;
string selectedClass;
if(action.Controller.Equals(currentController, StringComparison.InvariantCultureIgnoreCase)
{
selectedClass = "selected-parent";
if(action.Action.Equals(currentAction, StringComparison.InvariantCultureIgnoreCase))
selectedClass = "selected";
}
cssClass += selectedClass;
attributes["class"] = cssClass;
return helper.ActionLink(text, (ActionResult)action, attributes);
}
It might just be that it's the 5th parameter, so slot a null before your html attribute. This post here describes it as such, though you can pass in some stuff on the 4th arguement, the 5th is specifically for HTMLattributes
<script type="javascript/text">
$( document ).ready( function() {
#if (Request.Url.AbsolutePath.ToLower() == "/")
{
#Html.Raw("$('.navbar-nav li').eq(0).attr('class','active');")
}
#if (Request.Url.AbsolutePath.ToLower().Contains("details"))
{
#Html.Raw("$('.navbar-nav li').eq(1).attr('class','active');")
}
#if (Request.Url.AbsolutePath.ToLower().Contains("schedule"))
{
#Html.Raw("$('.navbar-nav li').eq(2).attr('class','active');")
}
});
</script>
Chucked this together in 5mins, I could probably refactor it, but should give you the basic idea, its probably most useful for smaller sites.