将“active”标记添加到asp.net mvc母版页的导航列表中

时间:2008-10-18 04:42:25

标签: 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是什么。

有关如何执行此操作的任何建议?或者,如果我的轨道完全错误,有没有更好的方法呢?

15 个答案:

答案 0 :(得分:37)

我自己做了一个辅助方法来处理这类事情。在我的母版页后面的代码中(可以推送到扩展方法......可能是更好的方法),我把下面的代码。

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")%>

答案 1 :(得分:10)

在视图中,您可以使用以下命令获取当前操作名称:

ViewContext.RouteData.Values["action"].ToString()

答案 2 :(得分:10)

您还可以尝试从控制器名称和视图名称中检测当前选定的选项卡,然后添加类属性。

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

答案 3 :(得分:10)

在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和它的控制器具有相同的名称索引,因此我得到控制器名称以区分操作行为。如果您想采取行动,只需替换为以下内容:

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

答案 4 :(得分:6)

为了贡献我自己的答案(在MVC4中测试),我采取了其他答案的一些最佳位,解决了一些问题,并添加了一个帮助程序来处理不一定通过Controller&amp;动作(例如,如果您有嵌入式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));
}

答案 5 :(得分:5)

一个老问题,但希望有人可能会发现这非常有帮助。

  1. ViewBag 中添加一些可用于识别页面的内容,我使用了 ViewgBag.PageName
  2. 例如,在 index.cshtml 中,添加类似

    的内容
    @{
        ViewBag.PageName = "Index";
    }
    
    1. 使用条件语句向每个链接项添加一个类,以便在访问的页面具有所需值时返回活动,否则返回空字符串。查看以下详细信息:
    2. <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>

      我不只是测试它,我在我的项目中使用这个方法

答案 6 :(得分:4)

将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类名。

答案 7 :(得分:1)

这应该可以在客户端使用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>

答案 8 :(得分:1)

我希望能够更好地控制我的布局,这就是我所做的。

创建其他模型继承的LayoutModel:

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

创建一个继承自ActionFilterAttribute的LayoutAttribute,如下所示:

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的任何其他内容。

答案 9 :(得分:0)

您的View必须了解控制器的操作这一事实正在打破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 --><% } %>

然而,我越是想到它,我就越怀疑你为什么使用相同的View进行多项操作。视图 是否相似?

如果用例证明它是合理的,那么请按照我的上述建议。但是否则你可能会把事情分解成多个视图(每个控制器动作一个),问题就解决了。

答案 10 :(得分:0)

根据之前的答案,以下是我目前针对同一问题的解决方案:

在母版页中,我给每个li一个与控制器和动作相对应的id,因为这应该从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的所有操作。

如果有人发现这种方法有问题,请告诉我。

答案 11 :(得分:0)

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

答案 12 :(得分:0)

这是与当前版本的MVC4兼容的版本 我已经将Adam Carr的代码重写为扩展方法。

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

答案 13 :(得分:0)

希望这会有所帮助。

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

答案 14 :(得分:-1)

尝试

应该可以正常工作!!!

编辑:在BETA1中删除

从ViewContext类中删除了ViewName属性。