select right menu on master page in MVC from child page - asp.net-mvc

I have a couple of list items in a shared _layout.cshtm file (master page) in my MVC application.
something like:
<ul>
<li>Home</li>
<li>about</li>
<li>contact</li>
<li>blog</li>
</ul>
when the user is in a homepage, I want home li item to have class selected, like so:
<li class="selected">Home</li>
and so on. What is the best way to do this?
In regular asp.net website, I used to have a method in master page and call that method from child page but in MVC I am not sure what to do.
thanks.

You could write a custom helper method:
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string text,
string action,
string controller
)
{
var li = new TagBuilder("li");
var routeData = htmlHelper.ViewContext.RouteData;
var currentAction = routeData.GetRequiredString("action");
var currentController = routeData.GetRequiredString("controller");
if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
{
li.AddCssClass("selected");
}
li.SetInnerText(text);
return MvcHtmlString.Create(li.ToString());
}
and then:
<ul>
#Html.MenuItem("Home", "home", "home")
#Html.MenuItem("About", "about", "home")
#Html.MenuItem("Contact", "contact", "home")
#Html.MenuItem("Blog", "blog", "home")
</ul>
The helper check the current action and controller and if they match the one passed as arguments to the helper it appends the selected CSS class to the li.

Just wanted to share what i do:
I create folder App_Code and add CustomHelpers.cshtml. In it i create something like this:
#helper MainMenu(string SelectedItem) {
<ul class="MainMenu">
<li><a href="/home" #if (SelectedItem == "Home") { <text>class="Active"</text> }>Home</a></li>
<li><a href="/about" #if (SelectedItem == "About") { <text>class="Active"</text> }>About</a></li>
<li><a href="/foo" #if (SelectedItem == "Foo") { <text>class="Active"</text> }>Foo</a></li>
</ul>
}
Than in my MasterPage (_Layout.cshtml) i add this where i want my menu to apear:
#CustomHelpers.MainMenu(ViewBag.SelectedMenu)
And than in my view, just like i change my page title, i change my selected menu:
#{
ViewBag.Title = "Welcome to my homepage";
ViewBag.SelectedMenu = "Home";
}
Got my idea from this tutorial: www.asp.net/mvc/videos/mvc-3/mvc-3-razor-helpers

Related

MVC 5 Razor Navigation Active CLass

I am trying to get the active class to show on my navigation. I have written the htmlhelper:
public static string IsActive(this HtmlHelper htmlHelper, string controller, string action)
{
var routeData = htmlHelper.ViewContext.RouteData;
var routeAction = routeData.Values["action"].ToString();
var routeController = routeData.Values["controller"].ToString();
var returnActive = (controller == routeController && action == routeAction);
return returnActive ? "active" : "";
}
and this is my navigation:
<li #Html.IsActive("MemberDashboard", "Index")><span class="fa fa-list-alt fa-lg fa-fw"></span> Accounts</li>
<li #Html.IsActive("Transfer", "Index")><span class="fa fa-retweet fa-lg fa-fw"></span> Transfers</li>
The navigation doesn't go to active when I'm on said page.
There's a few things here that I can recommend:
You are outputting the result of #Html.IsActive() as an attribute on the <li> element. I think you'd want to assign this to the class atribute of the element.
I would compare controller and action strings using invariant culture and ignoring case to cover the likely strings you want to successfully match against.
I would return null from the method in the case the controller and action strings do not match the current controller and action. If null is returned and assigned to the class attribute of the <li> element, then the class attribute will not be outputted at all in the markup. Small difference, but perhaps a little cleaner than an empty class attribute.
I would match the order of controller and action in the method signature to the order they appear in framework methods. This will be more intuitive for others to use and less confusing in the long run.
So, applying all of the above
public static string IsActive(this HtmlHelper htmlHelper, string action, string controller)
{
var routeData = htmlHelper.ViewContext.RouteData;
var routeAction = (string)routeData.Values["action"];
var routeController = (string)routeData.Values["controller"];
var isActive = string.Equals(controller, routeController, StringComparison.InvariantCultureIgnoreCase)
&& string.Equals(action, routeAction, StringComparison.InvariantCultureIgnoreCase);
return isActive ? "active" : null;
}
and usage
<li class="#(Html.IsActive("Index", "MemberDashboard"))">
<a href="#Url.Action("Index", "MemberDashboard")">
<span class="fa fa-list-alt fa-lg fa-fw"></span> Accounts
</a>
</li>
<li class="#(Html.IsActive("Index", "Transfer"))">
<a href="#Url.Action("Index", "Transfer")">
<span class="fa fa-retweet fa-lg fa-fw"></span> Transfers
</a>
</li>
You may want to also include an area string too if you're planning on using areas.

MVC with Bootstrap Navbar - Set Selected Item to Active

I'm learning Bootstrap and can't get the selected item into an "active" state. The active state remains on the default item. The newly selected/clicked item changes to active briefly, but reverts back. I've read all the posts and still can't get this code to work. I'm using MVC5 and JQuery 2.1.
EDIT:
If I change the li's hrefs to href="#", then the active class gets applied properly. What's happening when a new view gets loaded? I think Sebastian's response is close, but gets messy with Areas.
Markup
<div class="navbar-wrapper">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-header">
<a class="navbar-toggle" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="navbar-brand" href="#">Test</a>
</div>
<div class="btn-group pull-right">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-user"></i>Login
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>Profile</li>
<li class="divider"></li>
<li>Sign Out</li>
</ul>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>About</li>
<li>Students Sample</li>
<li class="dropdown">
Dropdown <b class="caret"></b>
<ul class="dropdown-menu">
<li>Admin</li>
<li>Another action</li>
<li>Something else here</li>
<li class="divider"></li>
<li>Separated link</li>
<li>One more separated link</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<!-- /container -->
</div>
<!-- /navbar wrapper -->
Script
<script type="text/javascript">
$(function () {
$('.navbar-nav li').click(function () {
$(this).addClass('active').siblings().removeClass('active');
});
});
</script>
EDIT: Here's what I ended up doing with the help of the posted answers and some research.
public static string MakeActive(this UrlHelper urlHelper,string action, string controller, string area = "")
{
string result = "active";
string requestContextRoute;
string passedInRoute;
// Get the route values from the request
var sb = new StringBuilder().Append(urlHelper.RequestContext.RouteData.DataTokens["area"]);
sb.Append("/");
sb.Append(urlHelper.RequestContext.RouteData.Values["controller"].ToString());
sb.Append("/");
sb.Append(urlHelper.RequestContext.RouteData.Values["action"].ToString());
requestContextRoute = sb.ToString();
if (string.IsNullOrWhiteSpace(area))
{
passedInRoute = "/" + controller + "/" + action;
}
else
{
passedInRoute = area + "/" + controller + "/" + action;
}
// Are the 2 routes the same?
if (!requestContextRoute.Equals(passedInRoute, StringComparison.OrdinalIgnoreCase))
{
result = null;
}
return result;
}
You have to check in your controller or view which menu item is active based on the current url:
I have an extension method similar to this:
public static string MakeActiveClass(this UrlHelper urlHelper, string controller)
{
string result = "active";
string controllerName = urlHelper.RequestContext.RouteData.Values["controller"].ToString();
if (!controllerName.Equals(controller, StringComparison.OrdinalIgnoreCase))
{
result = null;
}
return result;
}
You can use it in your view like this:
<!-- Make the list item active when the current controller is equal to "blog" -->
<li class="#Url.MakeActive("blog")">
....
</li>
The JavaScript isn't working because the page is getting reloaded after it runs. So it correctly sets the active item and then the page loads because the browser is following the link. Personally, I would remove the JavaScript you have because it serves no purpose. To do this client side (instead of the server side code you have), you need JavaScript to set the active item when the new page loads. Something like:
$(document).ready(function() {
$('ul.nav.navbar-nav').find('a[href="' + location.pathname + '"]')
.closest('li').addClass('active');
});
I recommend adding an id or other class to your navbar so you can be sure you have selected the correct one.
Simplest thing to do is send a ViewBag parameter from your controllers like following;
public ActionResult About()
{
ViewBag.Current = "About";
return View();
}
public ActionResult Contact()
{
ViewBag.Current = "Contact";
return View();
}
In the cshtml page do the following;
<ul class="nav navbar-nav">
<li class="#(ViewBag.Current == "About" ? "active" : "")">#Html.ActionLink("About", "About", "Home")</li>
<li class="#(ViewBag.Current == "Contact" ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
Courtesy to #Damith in here
Simply you can do this in any view
<ul class="nav navbar-nav">
#Html.NavigationLink("Link1", "Index", "Home")
#Html.NavigationLink("Link2", "Index", "Home")
#Html.NavigationLink("Link3", "Index", "Home")
#Html.NavigationLink("Links with Parameter", "myAction", "MyController", new{ id=999}, new { #class= " icon-next" })
</ul>
After you add this method to a new class or existing HtmlExtensions class
public static class HtmlExtensions
{
public static MvcHtmlString NavigationLink(this HtmlHelper html, string linkText, string action, string controller, object routeValues=null, object css=null)
{
TagBuilder aTag = new TagBuilder("a");
TagBuilder liTag = new TagBuilder("li");
var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(css);
string url = (routeValues == null)?
(new UrlHelper(html.ViewContext.RequestContext)).Action(action, controller)
:(new UrlHelper(html.ViewContext.RequestContext)).Action(action, controller, routeValues);
aTag.MergeAttribute("href", url);
aTag.InnerHtml = linkText;
aTag.MergeAttributes(htmlAttributes);
if (action.ToLower() == html.ViewContext.RouteData.Values["action"].ToString().ToLower() && controller.ToLower() == html.ViewContext.RouteData.Values["controller"].ToString().ToLower())
liTag.MergeAttribute("class","active");
liTag.InnerHtml = aTag.ToString(TagRenderMode.Normal);
return new MvcHtmlString(liTag.ToString(TagRenderMode.Normal));
}
}
I believe you have the selection backward. You're adding the class, then removing it from the siblings, and I think doing the remove second is causing the issue. Can you try reversing this to be:
<script type="text/javascript">
$(function () {
$('.navbar-nav li').click(function () {
$(this).siblings().removeClass('active');
$(this).addClass('active');
});
});
</script>
Your javascript function should work fine... The issue is that your links route to a controller and reload the entire page. In order to avoid this behavior you could render your body content as a partial view, that way the navbar elements do not reload. You shouldn't have to write a function to handle dom events - that is what javascript is for.
To see what I mean, change your code:
<li>About</li>
<li>Students Sample</li>
to:
<li>About</li>
<li>Students Sample</li>
Just add this JQuery coded :
<script>
$(document).ready(function () {
$('body').find('a[href="' + location.pathname + '"]')
.addClass('active');
});
</script>
With this simplified version of the code from here, you can use a tag helper to mark the anchor 'active' if the controller & action match (or just the controller if no action is supplied).
The nav:
<li class="nav-item px-2">
<a class="nav-link" asp-active-route asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item px-2">
<a class="nav-link" asp-active-route asp-controller="Car" asp-action="Add">Add Car</a>
</li>
The tag helper class
[HtmlTargetElement(Attributes = "asp-active-route")]
public class ActiveRouteTagHelper : TagHelper
{
[HtmlAttributeName("asp-controller")] public string Controller { get; set; }
[HtmlAttributeName("asp-action")] public string Action { get; set; }
[HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (IsActive())
{
output.AddClass("active", HtmlEncoder.Default);
}
output.Attributes.RemoveAll("asp-active-route");
base.Process(context, output);
}
private bool IsActive()
{
var currentController = ViewContext.RouteData.Values["Controller"].ToString();
var currentAction = ViewContext.RouteData.Values["Action"].ToString();
var active = false;
if (!string.IsNullOrWhiteSpace(Controller) && !string.IsNullOrWhiteSpace(currentController))
{
active = Controller.Equals(currentController, StringComparison.CurrentCultureIgnoreCase);
if (active && !string.IsNullOrWhiteSpace(Action) && !string.IsNullOrWhiteSpace(currentAction))
{
active = Action.Equals(currentAction, StringComparison.CurrentCultureIgnoreCase);
}
}
return active;
}
}
Don't forget to add the tag helper to your _ViewImports.cshtml
#addTagHelper *, My.Awesome.Web
Future improvement would be to also check areas and possibly wrap the entire nav in a control/helper so you only have to compare the routes once and flag the item
You can use Bootstrap's nav-pills to change the color of active menu item.
Bootstrap's nav-pills to change the color of active menu item
Here is a simple solution that works - store class name in TempData:
In each action of the controller add one line:
// add "active" class to nav-item TempData["Home"] = "active";
In Layout view nav item:
<a class="nav-link #TempData["Home"]">Home</a>
Thus only one nav-item will get class "active"

ASP.NET MVC4 menu link activate when controller and action matches

I have a ASP.NET MVC4 application and my left navigation menu is under a CommonController so from my _Layout.cshtml I render it like:
<div class="leftmenu">
#Html.Action("LeftMenu", "Common")
</div><!--leftmenu-->
Now, in my partial view LeftMenu I have all the HTML code for displaying the menu items and what I want to do is to highlight the right menu item based on the current page controller and action.
LeftMenu.cshtml
<ul class="nav nav-tabs nav-stacked">
<li class="nav-header">Navigation Menu</li>
<li>#Html.MenuLink("Test Link", "Index", "Home", "active",true)</li>
......
Right now Im using the following helper code:
public static HtmlString MenuLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string activeClass, bool checkAction)
{
string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
if (string.Compare(controllerName, currentController, StringComparison.OrdinalIgnoreCase) == 0 && ((!checkAction) || string.Compare(actionName, currentAction, StringComparison.OrdinalIgnoreCase) == 0))
{
return htmlHelper.ActionLink(linkText, actionName, controllerName, null, new { #class = activeClass });
}
return htmlHelper.ActionLink(linkText, actionName, controllerName);
}
The issue that Im having is that the currentController is "Common" because its where LeftMenu partial view is located.
I need to get the Main Page current controller and current action in order to make it work.
Any clue or advise on how to solve it?
Thanks a lot
I recommend moving your "leftMenu.cshtml" to the Shared views folder beside your _Layout.cshtml and using it in your menu like this:
<div class="leftmenu">
#Html.Partial("LeftMenu")
</div>
<!--leftmenu-->
this way you will get the proper action name in your custom Helper.

Applying styles programmatically in ASP.NET MVC MasterPage View

What would be the best/most suitable way to achieve the following in my MVC web application ?
I have a view called Site.Master (it's my MasterPage view), and across the top of this view, I have 5 links, which act as my main site navigation e.g.
<ul>
<li>Home</li>
<li>Links</li>
<li>Contact Us</li>
...etc
</ul>
What I want is to be able to highlight the appropriate text link, according to which part of the site the user is currently viewing, so if they were using the 'Contact Us' page, the Contact Us link on the master page view would have a different css style applied to it.
In Web Forms, each of my links was a HyperLink control, and I had a property in the code behind of the MasterPage so assign the relevant CssStyle to each HyperLink control.
What would be the best way to achieve the same thing in my MasterPage view, now I'm using MVC?
I would probably write an html helper which will generate those menu links and based on the current controller and action will apply a css class current to the anchor:
public static MvcHtmlString MenuLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName
)
{
string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
if (actionName == currentAction && controllerName == currentController)
{
return htmlHelper.ActionLink(
linkText,
actionName,
controllerName,
null,
new {
#class = "current"
});
}
return htmlHelper.ActionLink(linkText, actionName, controllerName);
}
And then use this helper in my view:
<ul>
<li><%= Html.MenuLink("Home", "Index", "Home") %></li>
<li><%= Html.MenuLink("Links", "Links", "Home") %></li>
<li><%= Html.MenuLink("Contact us", "Contact", "Home") %></li>
</ul>
Then all that's left is to define this current class in a CSS file to highlight:
.current {
...
}

Adding "active" tag to navigation list in an asp.net mvc master page

In the default asp.net mvc project, in the Site.Master file, there is a menu navigation list:
<div id="menucontainer">
<ul id="menu">
<li><%= Html.ActionLink("Home", "Index", "Home")%></li>
<li><%= Html.ActionLink("About Us", "About", "Home")%></li>
</ul>
</div>
This renders in the browser to:
<div id="menucontainer">
<ul id="menu">
<li>Home</li>
<li>About Us</li>
</ul>
</div>
I want to be able to dynamically set the active list item, based on the view that is being called. That is, when the user is looking at the home page, I would want the following HTML to be created:
<div id="menucontainer">
<ul id="menu">
<li class="active">Home</li>
<li>About Us</li>
</ul>
</div>
I would expect that the way to do this would be something like:
<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>
The key bit here is the <% if(actionName == "Index"){%> class="active"<%}%> line. I do not know how to determine what the current actionName is.
Any suggestions on how to do this? Or, if I'm on completely the wrong track, is there a better way to do this?
I made myself a helper method to handle this type of thing. In the code behind of my master page (could be pushed of to an extension method ... probably a better approach), I put the following code.
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);
}
Then, I just call it in my page like so:
<%= ActiveActionLinkHelper("Home", "Index", "Home", "selected")%>
Inside a view, you can get the current action name with:
ViewContext.RouteData.Values["action"].ToString()
You can also try to detect which is the current selected tab from its controller name and view name, then add the class attribute.
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);
}
In MVC 3 Razor View Engine, you can do it as:
#{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>
My sample worked when I have two pages as: Home/About and its controller has same name Index, so I get controller Name for distinction insteed of action. If you want to get action, just replace with following:
#{string ctrName = ViewContext.RouteData.Values["action"].ToString();}
An old question but hopefully someone might find this very helpful.
Put some thing that you can use to identify your page in the ViewBag, I used ViewgBag.PageName
For example, in index.cshtml, put something like
#{
ViewBag.PageName = "Index";
}
Add a class to each link item with a conditional statement to return active if the page being visited has the required value, or return an empty string otherwise. View below for details:
<li class="#((ViewBag.PageName == "Index") ? "active" : "")">Home</li>
<li class="#((ViewBag.PageName == "About") ? "active" : "")">About</li>
<li class="#((ViewBag.PageName == "Contact") ? "active" : "")">Contact</li>
I didn't just test it, I use this method in my projects
To contribute my own answer (tested in MVC4), I took a few best bits of the other answers, fixed a few issues, and added a helper to work with urls that aren't necessarily resolved via Controller & Action (eg. if you have an embedded CMS dealing with some page links, etc.)
The code is also forkable on 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));
}
Using MVC3 with a Razor View offers another option:
_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();
}
If you want to preserve this for a postback as well, you have to assign the ViewBag value here as well:
[HttpPost]
public ActionResult Index() {
ViewBag.NavClassHome = "active";
return View();
}
[HttpPost]
public ActionResult About() {
ViewBag.NavClassAbout = "active";
return View();
}
Tested and working fine for me, but you will have a css class name in your server side code.
This should work using jQuery on the client side of things, uses Google to serve the latest jQuery library:
<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>
I wanted to have a bit more control over my layout, and this is what I did.
Create a LayoutModel that other models inherit:
public abstract class LayoutModel
{
public CurrentPage CurrentPage { get; set; }
}
Create a LayoutAttribute that inherits from ActionFilterAttribute like so:
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;
}
}
Now on the Action or Controller level I can set the current page (and other stuff if I wanted) like this:
[Layout(CurrentPage.Account)]
public class MyController : Controller
{
}
In my layout view I now have access to the current page, and whatever else I add to the LayoutModel.
The fact that your View has to know about your controller's actions is breaking with the MVC pattern. Perhaps your controller could pass some "control" information to the view to ultimately allow it to accomplish the same thing, the only difference is who is in charge.
Like in your controller's action you could:
public ActionResult Index(){
ViewData["currentAction"] = "Index";
//... other code
return View();
}
Then over in your view you could:
<% if( ((string)ViewData["currentAction"]) == "Index" {%> <!- some links --><% } %>
<% if( ((string)ViewData["currentAction"]) == "SomethingElse" {%> <!- some links --><% } %>
However, the more I think about it the more I question why you are using the same View for multiple actions. Is the view that similar?
If the use case justifies it then go with my above suggestion. But otherwise perhaps you could break things out into multiple views (one for each controller action) and the problem solves itself.
Based on the previous answers, here is what my current solution is for the same issue:
In the master page I give each li an id that corresponds to the controller and the action, since this should be known from the ActionLink. I was previously doing this with the page title but this helps with organization.
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");
}
}
It's more work than the inline code but I think it's cleaner and also lets you have actions with the same name in different controllers. So if you add more menu items with different controllers, not all actions named Index will be highlighted in the menu.
If anyone sees issues with this approach please let me know.
Using MVC3 with a Razor View, you can implement this like:
<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>
And then applying your style of your ".active" class like:
ul#menu li.active
{
text-decoration:underline;
}
Here is the version compatible with the current version of MVC4.
I have rewritten Adam Carr's code as an extension method.
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);
}
}
}
Hope this will help.
<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>
Try
Should work fine !!!
EDIT : REMOVED IN BETA1
Removed the ViewName property from the ViewContext class.

Resources