可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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\")\">
<a href=\"@Url.Action(\"Home\", \"Default\")\">Home</a>
</li>
<li class=\"@Html.IsSelected(actions: \"List,Detail\", controllers: \"Default\")\">
<a href=\"@Url.Action(\"List\", \"Default\")\">List</a>
</li>
</ul>
回答2:
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>
回答3:
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\" : \"\") \"><a href=\"@Url.Action(\"Index\",\"Scheduler\")\" target=\"_self\">Scheduler</a></li>
</ul>
This solved my problem.
回答4:
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
回答5:
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.
回答6:
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>
回答7:
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\")\">
<a href=\"@Url.Action(\"Index\", \"Home\")\"><i class=\"fa fa-link\"></i> <span>Home</span></a>
</li>
<li class=\"@Url.IsLinkActive(\"About\", \"Home\")\">
<a href=\"@Url.Action(\"About\", \"Home\")\"><i class=\"fa fa-link\"></i><span>About</span></a>
</li>
</ul>
回答8:
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>
--
回答9:
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\")})
回答10:
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
回答11:
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>
回答12:
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\":\"\")\">
<a href=\"@Url.Action(\"Index\",\"Home\")\">Dashboard</a>
</li>
<li class=\"@(headerTop==HeaderTop.Product?\"active selected open\":\"\")\">
<a href=\"@Url.Action(\"Index\", \"Products\")\">Products</a>
</li>
</ul>
More Info: https://github.com/betalgo/MvcMenuNavigator
回答13:
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\"><a href=\"#\">Home</a></li>
<li><a href=\"#\">Profile</a></li>
<li><a href=\"#\">Messages</a></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>
回答14:
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.
回答15:
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.
回答16:
Hope this helps, below is how your menu can be:
<ul class=\"nav navbar-nav\">
<li><a href=\"@Url.Action(\"Index\", \"Home\")\" class=\"@IsMenuSelected(\"Index\",\"Home\", \"active\")\">Home</a></li>
<li><a href=\"@Url.Action(\"About\", \"Home\")\" class=\"@IsMenuSelected(\"About\", \"Home\", \"active\")\">About</a></li>
<li><a href=\"@Url.Action(\"Contact\", \"Home\")\" class=\"@IsMenuSelected(\"Contact\", \"Home\", \"active\")\">Contact</a></li>
</ul>
And add below MVC helper function below the html tag.
@helper IsMenuSelected(string actionName, string controllerName, string className)
{
var conname = ViewContext.RouteData.Values[\"controller\"].ToString().ToLower();
var actname = ViewContext.RouteData.Values[\"action\"].ToString().ToLower();
if (conname == controllerName.ToLower() && actname == actionName.ToLower())
{
@className;
}
}
I referred the answer from http://questionbox.in/add-active-class-menu-link-html-actionlink-asp-net-mvc/
回答17:
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.
回答18:
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.
回答19:
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
回答20:
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\" : \"\")\"><a href=\"index.html\">Dashboard v1</a></li>
<li class=\"@(ViewBag.Title == \"Dashboard 2\" ? \"active\" : \"\")\"><a href=\"index_v2.html\">Dashboard v2</a></li>
</ul>
</li>
回答21:
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\"
: \"\";
}