Добавление активного тега в список навигации на главной странице asp.net mvc

В проекте asp.net mvc по умолчанию в файле Site.Master есть список навигации по меню:

<div id="menucontainer">
    <ul id="menu">              
        <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
        <li><%= Html.ActionLink("About Us", "About", "Home")%></li>
    </ul>
</div>

Это отображает в браузере:

<div id="menucontainer"> 
    <ul id="menu">              
        <li><a href="/">Home</a></li> 
        <li><a href="/Home/About">About Us</a></li> 
    </ul> 
</div> 

Я хочу иметь возможность динамически устанавливать активный элемент списка в зависимости от вызываемого представления. То есть, когда пользователь смотрит на домашнюю страницу, я бы хотел, чтобы был создан следующий HTML-код:

<div id="menucontainer"> 
    <ul id="menu">              
        <li class="active"><a href="/">Home</a></li> 
        <li><a href="/Home/About">About Us</a></li> 
    </ul> 
</div> 

Я ожидал, что это будет примерно так:

<div id="menucontainer">
    <ul id="menu">              
        <li <% if(actionName == "Index"){%> class="active"<%}%>><%= Html.ActionLink("Home", "Index", "Home")%></li>
        <li <% if(actionName == "About"){%> class="active"<%}%>><%= Html.ActionLink("About Us", "About", "Home")%></li>
    </ul>
</div>

Ключевой бит здесь - строка <% if(actionName == "Index"){%> class="active"<%}%>. Я не знаю, как определить текущее actionName.

Есть предложения, как это сделать? Или, если я ошибаюсь, есть ли лучший способ сделать это?


person dp.    schedule 18.10.2008    source источник


Ответы (15)


Я создал себе вспомогательный метод для решения подобных проблем. В коде за моей главной страницей (может быть перенесен в метод расширения ... возможно, лучший подход) я поместил следующий код.

protected string ActiveActionLinkHelper(string linkText, string actionName, string controlName, string activeClassName)
{
    if (ViewContext.RouteData.Values["action"].ToString() == actionName && 
            ViewContext.RouteData.Values["controller"].ToString() == controlName)
        return Html.ActionLink(linkText, actionName, controlName, new { Class = activeClassName });

    return Html.ActionLink(linkText, actionName, controlName);
}

Затем я просто называю это на своей странице так:

<%= ActiveActionLinkHelper("Home", "Index", "Home", "selected")%>
person Adam Carr    schedule 14.12.2008
comment
новый {Class = selected} должен быть новым {Class = activeClassName}; P - person Michal Ciechan; 03.08.2013
comment
Потрясающий. Просто искал это сам. - person Steven; 04.07.2014

Внутри представления вы можете получить имя текущего действия с помощью:

ViewContext.RouteData.Values["action"].ToString()
person Craig Stuntz    schedule 22.10.2008

Вы также можете попытаться определить текущую выбранную вкладку по имени контроллера и имени представления, а затем добавить атрибут класса.

public static string MenuActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName)
{
    var htmlAttributes = new RouteValueDictionary();

    if (helper.ViewContext.Controller.GetType().Name.Equals(controllerName + "Controller", StringComparison.OrdinalIgnoreCase))
    {
        htmlAttributes.Add("class", "current");
    }

    return helper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), htmlAttributes);
}
person labilbe    schedule 28.11.2008
comment
Если вы получаете сообщение об ошибке при возврате helper.ActionLink, добавьте это в свои ссылки using: using System.Web.Mvc.Html; - person Mike; 17.11.2012
comment
он должен возвращать MvcHtmlString, а не строку - person BjarkeCK; 28.11.2012

В MVC 3 Razor View Engine это можно сделать как:

@{string ctrName = ViewContext.RouteData.Values["controller"].ToString();}

<div id="menucontainer">
  <ul id="menu"> 
    <li @if(ctrName == "Home"){<text> class="active"</text>}>@ Html.ActionLink("Home",  "Index", "Home")</li>
    <li @if(ctrName == "About"){<text> class="active"</text>}>@ Html.ActionLink("About Us", "About", "Home")</li>
  </ul>
</div>

Мой образец работал, когда у меня было две страницы: Home / About, а его контроллер имеет то же имя Index, поэтому я получаю имя контроллера для различения, гарантирующего действие. Если вы хотите получить действие, просто замените следующим:

@{string ctrName = ViewContext.RouteData.Values["action"].ToString();}
person Telvin Nguyen    schedule 29.11.2011
comment
также, вероятно, стоит сделать что-то вроде: @if (ctrName.ToLower () == home), чтобы учесть всех, кто вводит URL-адреса вручную. В противном случае вы обнаружите, что кто-то может посетить вашу страницу в / home (а не в / Home) и не увидеть меню, оформленное как активное. Обратите внимание: если вы действительно используете ToLower (), убедитесь, что ваша переменная ctrName никогда не может иметь значение null, иначе вы можете вызвать исключение. Например. определите ctrName как: string ctrName =; if (ViewContext.RouteData.Values ​​[действие]! = null) {ctrName = ViewContext.RouteData.Values ​​[действие] .ToString (); } - person Chris Matthews; 02.03.2017

Старый вопрос, но, надеюсь, кто-то сочтет это очень полезным.

  1. Поместите что-нибудь, что вы можете использовать для идентификации вашей страницы в ViewBag, я использовал ViewgBag.PageName

Например, в index.cshtml поместите что-нибудь вроде

@{
    ViewBag.PageName = "Index";
}
  1. Добавьте класс к каждому элементу ссылки с условным оператором, чтобы возвращать active, если посещаемая страница имеет требуемое значение, или возвращать пустую строку в противном случае. Подробности см. Ниже:

<li class="@((ViewBag.PageName == "Index") ? "active" : "")"><a href="@Url.Action("Index","Home")">Home</a></li>
<li class="@((ViewBag.PageName == "About") ? "active" : "")"><a href="@Url.Action("About","Home")">About</a></li>
<li class="@((ViewBag.PageName == "Contact") ? "active" : "")"><a href="@Url.Action("Contact","Home")">Contact</a></li>

Я не просто тестировал, я использую этот метод в своих проектах

person Nanan    schedule 29.06.2015
comment
Я обнаружил, что это единственное, что работало. Спасибо. Я использую MVC4. - person Gloria Santin; 01.10.2015

Чтобы внести свой собственный ответ (протестирован в MVC4), я взял несколько лучших частей других ответов, исправил несколько проблем и добавил помощника для работы с URL-адресами, которые не обязательно разрешаются с помощью Controller & Action (например, если вы иметь встроенную CMS, работающую со ссылками на некоторые страницы и т. д.)

Код также можно разветвлять на github: https://gist.github.com/2851684

/// 
/// adds the active class if the link's action & controller matches current request
/// 
public static MvcHtmlString MenuActionLink(this HtmlHelper htmlHelper,
    string linkText, string actionName, string controllerName,
    object routeValues = null, object htmlAttributes = null,
    string activeClassName = "active")
{
    IDictionary htmlAttributesDictionary =
        HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

    if (((string)htmlHelper.ViewContext.RouteData.Values["controller"])
            .Equals(controllerName, StringComparison.OrdinalIgnoreCase) &&
        ((string)htmlHelper.ViewContext.RouteData.Values["action"])
            .Equals(actionName, StringComparison.OrdinalIgnoreCase))
    {
        // careful in case class already exists
        htmlAttributesDictionary["class"] += " " + activeClassName;
    }

    return htmlHelper.ActionLink(linkText, actionName, controllerName,
                                    new RouteValueDictionary(routeValues),
                                    htmlAttributesDictionary);
}

/// 
/// adds the active class if the link's path matches current request
/// 
public static MvcHtmlString MenuActionLink(this HtmlHelper htmlHelper,
    string linkText, string path, object htmlAttributes = null,
    string activeClassName = "active")
{
    IDictionary htmlAttributesDictionary =
        HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
    if (HttpContext.Current.Request.Path
        .Equals(path, StringComparison.OrdinalIgnoreCase))
    {
        // careful in case class already exists
        htmlAttributesDictionary["class"] += " " + activeClassName;
    }
    var tagBuilder = new TagBuilder("a")
                            {
                                InnerHtml = !string.IsNullOrEmpty(linkText)
                                                ? HttpUtility.HtmlEncode(linkText)
                                                : string.Empty
                            };
    tagBuilder.MergeAttributes(htmlAttributesDictionary);
    tagBuilder.MergeAttribute("href", path);
    return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
person Tim Iles    schedule 01.06.2012

Использование MVC3 с Razor View предлагает другой вариант:

_Layout.cshtml:

<li class="@ViewBag.NavClassHome">@Html.ActionLink("Home", "Index", "Home")</li>
<li class="@ViewBag.NavClassAbout">@Html.ActionLink("Disclaimer", "About", "Home")</li>

HomeController:

public ActionResult Index() {
    ViewBag.NavClassHome = "active";
    return View();
} 

public ActionResult About() {
    ViewBag.NavClassAbout = "active";
    return View();
}

Если вы хотите сохранить это и для обратной передачи, вам также необходимо назначить здесь значение ViewBag:

[HttpPost]
public ActionResult Index() {
    ViewBag.NavClassHome = "active";
    return View();
}

[HttpPost]
public ActionResult About() {
    ViewBag.NavClassAbout = "active";
    return View();
}

Протестировано и отлично работает для меня, но у вас будет имя класса css в коде на стороне сервера.

person Anytoe    schedule 23.03.2014
comment
Я думал об этом решении перед поиском в Google, еще немного кода, но для моего небольшого веб-сайта это оказалось моим лучшим решением. Единственное, что у меня есть, это создать ViewBag.NavClass и продолжать использовать одну переменную вместо Home, About и т. Д. - person JoshYates1980; 31.03.2018

Это должно работать с использованием jQuery на стороне клиента, использует Google для обслуживания последней библиотеки jQuery:

<script src="http://www.google.com/jsapi" type="text/javascript" language="javascript"></script>
<script type="text/javascript" language="javascript">google.load("jquery", "1");</script>  

<script language="javascript" type="text/javascript">
      $(document).ready(function(){
          var str=location.href.toLowerCase(); 
        $('#menucontainer ul#menu li a').each(function() {
                if (str.indexOf(this.href.toLowerCase()) > -1) {
                        $(this).attr("class","current"); //hightlight parent tab
                     }  
                });
      });  
    </script>
person Slee    schedule 28.11.2008
comment
@ Брэди, почему бы и нет? Похоже на твердое решение. - person Edward Olamisan; 19.06.2014

Я хотел иметь немного больше контроля над своим макетом, и это то, что я сделал.

Создайте LayoutModel, который наследуют другие модели:

public abstract class LayoutModel
{
    public CurrentPage CurrentPage { get; set; }
}

Создайте LayoutAttribute, который наследуется от ActionFilterAttribute следующим образом:

public class LayoutAttribute : ActionFilterAttribute
{
    private CurrentPage _currentPage { get; set; }

    public LayoutAttribute(
        CurrentPage CurrentPage
    ){
        _currentPage = CurrentPage;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var result = filterContext.Result as ViewResultBase;
        if (result == null || result.Model == null || !(result.Model is LayoutModel)) return;

        ((LayoutModel)result.Model).CurrentPage = _currentPage;
    }
}

Теперь на уровне действия или контроллера я могу установить текущую страницу (и другие вещи, если захочу) следующим образом:

[Layout(CurrentPage.Account)]
public class MyController : Controller
{

}

В моем представлении макета теперь у меня есть доступ к текущей странице и ко всему, что я добавляю в LayoutModel.

person Bermy Dev    schedule 09.03.2013

Тот факт, что ваше представление должно знать о действиях вашего контроллера, нарушает шаблон MVC. Возможно, ваш контроллер может передать некоторую "управляющую" информацию в представление, чтобы в конечном итоге позволить ему выполнить то же самое, единственная разница в том, кто отвечает.

Как и в случае с вашим контроллером, вы можете:

public ActionResult Index(){
     ViewData["currentAction"] = "Index";
     //... other code
    return View();
}

Тогда, на ваш взгляд, вы могли:

<% if( ((string)ViewData["currentAction"]) == "Index" {%> <!- some links --><% } %>
<% if( ((string)ViewData["currentAction"]) == "SomethingElse" {%> <!- some links --><% } %>

Однако чем больше я думаю об этом, тем больше задаюсь вопросом, почему вы используете одно и то же представление для нескольких действий. Похоже ли мнение это?

Если вариант использования оправдывает это, следуйте моему предложению выше. Но в противном случае, возможно, вы могли бы разбить вещи на несколько представлений (по одному для каждого действия контроллера), и проблема решится сама собой.

person Jason Whitehorn    schedule 18.10.2008

Основываясь на предыдущих ответах, вот каково мое текущее решение для той же проблемы:

На главной странице я даю каждому li идентификатор, соответствующий контроллеру и действию, поскольку это должно быть известно из ActionLink. Раньше я делал это с заголовком страницы, но это помогает с организацией.

Site.Master:

<ul id="menu">
    <li id="menuHomeIndex" runat="server"><%= Html.ActionLink("Home", "Index", "Home") %></li>
    <li id="menuHomeAbout" runat="server"><%= Html.ActionLink("About Us", "About", "Home") %></li>
</ul>

Site.Master.cs:

// This is called in Page_Load
private void SetActiveLink()
{
    string action = "" + ViewContext.RouteData.Values["controller"] + ViewContext.RouteData.Values["action"];
    var activeMenu = (HtmlGenericControl)Page.Master.FindControl("menu" + action);

    if (activeMenu != null)
    {
        activeMenu.Attributes.Add("class", "selected");
    }
}

Это больше работы, чем встроенный код, но я думаю, что он чище, а также позволяет выполнять действия с одинаковыми именами в разных контроллерах. Поэтому, если вы добавляете больше пунктов меню с разными контроллерами, не все действия с именем Index будут выделены в меню.

Если кто-то обнаружит проблемы с этим подходом, дайте мне знать.

person Jonathan S.    schedule 23.10.2008

Используя MVC3 с Razor View, вы можете реализовать это следующим образом:

<ul id="menu">
 @if (ViewContext.RouteData.Values["action"].ToString() == "Index")
 {
 <li class="active">@Html.ActionLink("Home", "Index", "Home")</li>
 }
 else
 {
 <li>@Html.ActionLink("Home", "Index", "Home")</li>
 }
 @if (ViewContext.RouteData.Values["action"].ToString() == "About")
 {
 <li class="active">@Html.ActionLink("About", "About", "Home")</li>
 }
 else
 {
 <li>@Html.ActionLink("About", "About", "Home")</li>
 }
</ul>

А затем примените свой стиль вашего класса ".active", например:

ul#menu li.active 
{
 text-decoration:underline;
}
person Ricardo Vera    schedule 01.08.2011

Вот версия, совместимая с текущей версией MVC4.
Я переписал код Адама Карра как метод расширения.

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

namespace MyApp.Web {
    public static class HtmlHelpers {
        /// <summary>
        /// Returns an anchor element (a element) that contains the virtual path of the
        /// specified action. If the controller name matches the active controller, the
        /// css class 'current' will be applied.
        /// </summary>
        public static MvcHtmlString MenuActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName) {
            var htmlAttributes = new RouteValueDictionary();
            string name = helper.ViewContext.Controller.GetType().Name;

            if (name.Equals(controllerName + "Controller", StringComparison.OrdinalIgnoreCase))
                htmlAttributes.Add("class", "current");

            return helper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), htmlAttributes);
        }
    }
}
person Zyphrax    schedule 21.05.2012
comment
helper.ActionLink не компилируется - person Andy Brudtkuhl; 14.11.2012
comment
@AndyBrudtkuhl с использованием System.Web.Mvc.Html; получает его для компиляции - person m4chine; 05.09.2014

Надеюсь, это поможет.

<ul>
    <li class="@(ViewContext.RouteData.Values["Controller"].ToString() == "Home" ? "active" : "")">
        <a asp-area="" asp-controller="Home" asp-action="Index"><i class="icon fa fa-home"></i><span>Home</span>
        </a>
    </li>
</ul>
person Farhana    schedule 10.07.2020

Пытаться

Должно работать нормально !!!

РЕДАКТИРОВАТЬ: УДАЛЕНО В БЕТА1

Из класса ViewContext удалено свойство ViewName.

person Community    schedule 19.10.2008