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

我希望能够根据被调用的视图dynamic地设置活动列表项目。 也就是说,当用户正在查看主页时,我想要创build下面的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"<%}%> line。 我不知道如何确定当前的actionName是什么。

任何build议如何做到这一点? 或者,如果我完全错误的轨道,有没有更好的方法来做到这一点?

我自己做了一个帮手来处理这种事情。 在我的母版页后面的代码(可以推到一个扩展方法…可能是一个更好的办法),我把下面的代码。

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

在视图中,您可以通过以下方式获取当前操作名称:

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

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

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

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

为了提供我自己的答案(在MVC4中testing过),我花了一些其他答案的最佳位置,修复了一些问题,并添加了一个帮助器来处理不一定通过Controller&Action解决的url(例如,如果你有一个embedded式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)); } 

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

  1. 把一些东西,你可以用来确定你的网页在ViewBag ,我用ViewgBag.PageName

例如,在index.cshtml中 ,放一些类似的东西

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

在Razor View中使用MVC3提供了另一种select:

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

testing和工作对我来说很好,但是你的服务器端代码将会有一个css类的名字。

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

我想对自己的布局有更多的控制,这就是我所做的。

创build其他模型inheritance的LayoutModel:

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

创build一个从ActionFilterAttributeinheritance的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的其他内容。

你的视图必须知道你的控制器的行为的事实是打破了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 --><% } %> 

然而,我越想越多,我质疑为什么你使用相同的视图多个行动。 是相似的观点?

如果使用案例certificate了这一点,然后去我的以上build议。 但除此之外,也许你可以把事情分解成多个视图(每个控制器动作一个视图),问题就解决了。

根据以前的答案,这里是我目前的解决scheme是同一个问题:

在master页面中,我给每个li一个与控制器和action相对应的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的操作都会在菜单中高亮显示。

如果有人看到这种方法的问题,请让我知道。

使用MVC3的Razor视图,你可以像这样实现:

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

这里是与当前版本的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); } } } 

尝试

应该工作正常!

编辑:删除BETA1

从ViewContext类中移除了ViewName属性。