如何在ASP.NET MVC中为Html.ActionLink添加“active”类

150

我正在尝试在MVC中为我的Bootstrap导航栏添加"active"类,但是按照以下方式编写时不会显示活动类:

<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>

这看起来是一个格式正确的类,但却无法工作:

<a class="active" href="/">Home</a>
在Bootstrap文档中指出不应在导航栏中使用'a'标签,但上述方法是我认为正确的将类添加到Html.ActionLink的方式。还有其他(整洁)的方法可以做到这一点吗?

3
代码中含有class = active的解决方案。这不是你在问题中所说的吗?我添加类的方式就是按照你的代码编写。 - Matt Bodily
1
解决的代码有什么问题?你期望得到什么结果?比“似乎不起作用”更具体一些可以吗?你的问题现在不够清晰,无法提供帮助。 - dom
<ul class="nav navbar-nav active"> ???? - JC Lizard
2
刚才浏览了Bootstrap的文档,我认为你需要将active类添加到li元素而不是a。请参见此处的第一个示例:http://getbootstrap.com/components/#navbar - dom
是的,这就是我最初的想法,我只是试图在我的代码中保持样式的一致性,以便我可以采用可重复的方法将“active”类更改为所选的导航对象。 - Gillespie
显示剩余2条评论
28个回答

337
在Bootstrap中需要将active类应用于<li>元素而不是<a>元素。请参见此处的第一个示例:http://getbootstrap.com/components/#navbar 根据活跃或非活跃状态来处理UI样式与ASP.NET MVC的ActionLink助手无关。这是遵循Bootstrap框架构建方式的正确解决方案。
<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>

编辑:

由于您可能会在多个页面上重复使用菜单,因此最好有一种方法可以根据当前页面自动应用所选类别,而不是多次复制菜单并手动完成。

最简单的方法是仅使用ViewContext.RouteData中包含的值,即ActionController值。 我们可以基于您目前的情况构建以下内容:

<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>

在代码中可能不是很美观,但这样做可以完成任务,如果你想的话,还可以将菜单提取到部分视图中。有更加简洁的方法来完成这个任务,但因为你刚开始学习,所以我就说到这里了。祝你学习ASP.NET MVC顺利!


晚期编辑:

这个问题似乎有点热闹,所以我想加入一个更优雅的解决方案,使用HtmlHelper扩展。

编辑于03-24-2015:不得不重写此方法,以允许多个操作和控制器触发所选行为,并处理从子操作部分视图调用该方法的情况,我想分享更新!

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;
}

支持 .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;
}

使用示例:

<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>

谢谢,正如我之前提到的那样,这是我最初的想法,但我希望在我的代码中有一种方法可以在选定对象之间更改“active”类。 - Gillespie
2
@Gillespie 不用担心!我已经编辑了我的答案,添加了一些信息,可能会更有帮助。 - dom
6
关于第一个编辑,它实际上只对索引页面有效。看起来原因是在进行比较时,由于ViewContext.RouteData.Values["Action"]并未返回String类型的对象,导致失败(仍然不知道为什么它对第一页有效...)。当我将所有的ViewContext.RouteData.Values["Action"]更改为ViewContext.RouteData.Values["Action"].toString()时,所有页面都可以正常工作。以防有人遇到相同的问题,建议在您的解决方案中加入此内容。 - user3281466
2
@LonelyPixel 也许这个答案适合你? - dom
1
对于VB.NET用户:<li class="@(If(ViewContext.RouteData.Values("Action").ToString() = "Index", "active", ""))">@Html.ActionLink("主页", "Index", "Home")</li> - Andrea Antonangeli
显示剩余15条评论

30

扩展:

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);
}

用法:

<ul class="nav navbar-nav">
    @Html.LiActionLink("About", "About", "Home")
    @Html.LiActionLink("Contact", "Contact", "Home")
</ul>

1
这是一个非常高效的扩展。完美地工作,而且相当优雅。向您致敬,教授!我唯一的抱怨是 role="presentation",它对于主导航菜单(http://john.foliot.ca/aria-hidden/#pr)来说根本不合适。无序列表非常适合作为相关链接集的容器,因为它们以逻辑方式分组在一起。 - René Kåbis
@René Kåbis,这是关于Bootstrap导航的链接:http://getbootstrap.com/components/#nav-tabs - Prof
1
当用户手动输入小写字母或大写字母的URL时,例如“http://domain.com/Login”和“http://domain.com/LOGIN”,此代码无法正常工作。解决方案如下:var str = String.Format("
  • {1}
  • ", currentAction.toLower().Equals(action.toLower(), StringComparison.InvariantCulture) && currentController.toLower().Equals(controller.toLower(), StringComparison.InvariantCulture) ? " class=\"active\"" : String.Empty, html.ActionLink(text, action, controller).ToHtmlString() );
    - sky91

    21

    我通过在asp.net mvc中添加一个视图包参数来实现这一点。这是我所做的:

    在每个页面中添加像 ViewBag.Current = "Scheduler"; 这样的参数

    在布局页面中

    <ul class="nav navbar-nav">
         <li class="@(ViewBag.Current == "Scheduler" ? "active" : "") "><a href="@Url.Action("Index","Scheduler")" target="_self">Scheduler</a></li>
     </ul>
    

    这解决了我的问题。


    简单易行的解决方案。 - japes Sophey

    13

    ASP.NET Core 3.0和标签助手轻松上手

    [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");
                }
            }
        }
    }
    

    在_ViewImports.cs文件中加入以下内容:

    @addTagHelper *, YourAssemblyName
    

    使用说明:

     <li active-when="/Home/Index">
    

    是的,那对我有用,但是子列表有问题。我已经把它放了进去,但是在页面刷新后它没有保持列表展开。 - Mr.Buntha Khin
    <ul> <li> <ul> <li active-when="Home/Index">产品</li> <li active-when="Home/Category">类别</li> </ul> </li> </ul> - Mr.Buntha Khin
    对于第一层级,它可以正常工作,但是第二层级的ul无法工作。 - Mr.Buntha Khin
    你可以通过以下方式替换if、else和SetAttribute:output.MergeAttributes(new TagBuilder("li").AddCssClass("active")); - Finickyflame

    12

    可能有点晚了,但希望这可以帮到你。

    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" : "";
        }
    }
    

    以下是使用方法:

    <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>
    

    http://www.codingeverything.com/2014/05/mvcbootstrapactivenavbar.html获得参考。


    1
    非常简单和优雅的解决方案。我在这里进行了一些改进 https://gist.github.com/jean-rakotozafy/3508f9999671709523447a74dfd6dfbb 来突出显示所有控制器动作的菜单。 - Zan RAKOTO

    7

    ASP.NET Core & Bootstrap 4

    最新的答案

    我已经重新制作了@crush的整洁解决方案,基于一个IHtmlHelper扩展方法,以更新、ASP.NET CoreBootstrap 4兼容的方式来解决这个问题:

    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());
            }
        }
    }
    

    使用方法

    <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>
    

    1
    非常好的解决方案。在你的示例中,我如何将“联系人”默认设置为活动状态? - Md. Suman Kabir
    1
    谢谢。好的是,您不需要设置活动元素。一旦访问“联系”页面,ActiveActionLink()将自动将活动类应用于<li> :-) - ˈvɔlə
    1
    你的检查区在哪里? - Mohammad
    1
    如果你要在你的帮助程序中硬编码 nav-itemactive Bootstrap 类,那么你也可以把 nav-link 类硬编码进去,这样你就不需要每次都指定它了:new { @class = "nav-link" } - MarredCheese
    1
    如果我想要将活动类添加到锚元素,你的代码只适用于li元素。 - Dimitris Kougioumtzis
    显示剩余2条评论

    4

    我知道这个问题比较老,但是我想在这里说一下我的观点。我认为将链接是否可用的知识留给视图控制器是一个好主意。

    我会在控制器操作中为每个视图设置一个唯一值。例如,如果我想让主页链接可用,我会这样做:

    public ActionResult Index()
    {            
        ViewBag.Title = "Home";
        ViewBag.Home = "class = active";
        return View();
    }
    

    在我看来,我会写出类似这样的内容:
    <li @ViewBag.Home>@Html.ActionLink("Home", "Index", "Home", null, new { title = "Go home" })</li>
    

    当您导航到不同的页面(例如“程序”页面)时,ViewBag.Home不存在(而是存在ViewBag.Programs)。因此,什么都没有被呈现,甚至class =“”。我认为这样做更容易维护和清洁。我倾向于尽可能地将逻辑从视图中剥离出来。

    4

    考虑到Damith发布的内容,我认为你可以通过Viewbag.Title来限定活动状态(最佳实践是在内容页面中填充此内容,使_Layout.cshtml页面保留链接栏)。另外请注意,如果你使用子菜单项,它也可以正常工作:

    <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>
    

    干净高效! - mggluscevic

    3

    这个解决方案适用于Asp.net MCV 5。

    1. Create a static class, for example Utilitarios.cs.

    2. 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 "";
      }
      
    3. 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>
      

    3

    我创建了一个 HtmlHelper 扩展,为那些想要在链接本身而不是围绕链接的 <li> 中添加 "active" 类的人添加了一个 ActiveActionLink 方法。

    
    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;
        }
    }
    
    

    使用方法如下:

    @Html.ActiveActionLink("Home", "Index", "Home", new { area = "" }, new { @class = "nav-item nav-link" })
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接