ASP.NET MVC: Highlighting current link - asp.net

I need to highlight current link in menu, But structure of HTML is look like below:
<li class="active">
<a href='#Url.Action("MainPage", "Ticket")'>
<i class="fa fa-fw fa-home"></i>
HomePage
</a>
</li>
I know there are many different solutions to do that (+, +):
public static MvcHtmlString MenuLink(
this HtmlHelper helper,
string text, string action, string controller)
{
var routeData = helper.ViewContext.RouteData.Values;
var currentController = routeData["controller"];
var currentAction = routeData["action"];
if (String.Equals(action, currentAction as string,
StringComparison.OrdinalIgnoreCase)
&&
String.Equals(controller, currentController as string,
StringComparison.OrdinalIgnoreCase))
{
return helper.ActionLink(
text, action, controller, null,
new { #class = "active" }
);
}
return helper.ActionLink(text, action, controller);
}
But my HTML structure is a little bit different.
How can I do that?
Any idea?

Finally I changed the code this way and it works: (Thanks to #Stephen Muecke)
public static MvcHtmlString MenuLink(
this HtmlHelper helper,
string content, string action, string controller)
{
var routeData = helper.ViewContext.RouteData.Values;
var currentController = routeData["controller"];
var currentAction = routeData["action"];
var builder = new TagBuilder("li")
{
InnerHtml = content
};
if (String.Equals(action, currentAction as string,
StringComparison.OrdinalIgnoreCase)
&&
String.Equals(controller, currentController as string,
StringComparison.OrdinalIgnoreCase))
{
builder.AddCssClass("active");
return MvcHtmlString.Create(builder.ToString());
}
return MvcHtmlString.Create(builder.ToString());
}

Related

getting the returnUrl with parameters from another controller

I have a 'webshop' where you can buy all sorts of fruits, vedgetables and more. this website can be used in multiple languages.
when the user is looking for a specific item he's using a variable to filter through the items. the url will look like this localhost/Products?item=AARB.
If the user changes languages it will return the returnUrl. the returnUrl only returns the action method looking like localhost/Products. I want it so that the returnUrl also contains the query parameter as it is a lot more use friendly to go back to your searched item when changing languages.
My ProductsController has the following Index Method:
[HttpGet]
public ActionResult Index(string item = "APPE", int amount = 0)
{
var CurrentCulture = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
ViewData["amount"] = amount;
//Uses the Logged in Username to match the items related to it's RelatieNummer
var LoggedUser = Convert.ToInt32(User.Identity.Name);
UserCheck ApplicationUser = _context.ApplicationUser.Where(x => x.CompanyNumber == LoggedUser).First();
var itemCheck = item != null ? item.ToUpper() : "";
try
{
var currentCat = _context.Categories.Where(x => x.CategoryCode.Contains(itemCheck)).First();
ViewData["Main_Items"] = FillMainItems(); //This is a different Method which fills Commodity names
var SearchItem = _context.ZzProducts
.Where(x => x.RelatieNummer == LoggedUser)
.Where(x => x.HoofdSoortCode.Contains(itemCheck));
return View(SearchItem);
catch (Exception ex)
{
ModelState.AddModelError("Error", ex.Message);
return View("Error");
}
The querystring item is the users input. for example: He wants to look for banana's(BANA) or Oranges(MAND). when the user changes the language of the website, this querystring needs to be sent back as well, returning a full url of localhost/Products?item=BANA from, what I assume is my HomeController
In my HomeController I have made a method that changes the CultureInfo for the user.
[HttpGet]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
Console.WriteLine("The new CultureInfo is now: " + CultureInfo.CurrentCulture);
return LocalRedirect(returnUrl);
}
Hopefully I've made my intentions and code clear enough to help find a solution to my problem :)
Edit
I forgot to post my PartialView to show where I send my returnUrl back to the HomeController
#{
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
var returnUrl = string.IsNullOrEmpty(Context.Request.Path) ? "~" : $"~{Context.Request.Path.Value}";
}
//Clickable Flags for CultureInfo
<div id="SelectLanguage" title="#Localizer["Language"]">
<a asp-action="SetLanguage" asp-route-returnUrl="#returnUrl" asp-route-culture="nl-NL" asp-controller="Home">
<img src="~/css/Languages/nl.png" />
</a>
<a asp-action="SetLanguage" asp-route-returnUrl="#returnUrl" asp-route-culture="en-US" asp-controller="Home">
<img src="~/css/Languages/en.png" />
</a>
<a asp-action="SetLanguage" asp-route-returnUrl="#returnUrl" asp-route-culture="de-DE" asp-controller="Home">
<img src="~/css/Languages/de.png" />
</a>
<a asp-action="SetLanguage" asp-route-returnUrl="#returnUrl" asp-route-culture="da-DK" asp-controller="Home">
<img src="~/css/Languages/dk.png" />
</a>
</div>
You can add the value of the query string to the end of the path.
Here is the modified code:
var returnUrl = string.IsNullOrEmpty(Context.Request.Path) ? "~"
: $"~{Context.Request.Path.Value+ Context.Request.QueryString.Value}";
in your View you have a Codepart where you define the returnUrl you then proceed to give this to your HomeController where you set the language.
you can useContext.Request.Path to also find the value of your querystring.
//Checks if the path is empty. If not, check the value of your QueryString
var parameters = string.IsNullOrEmpty(Context.Request.Path) ? "" : $"{Context.Request.QueryString.Value }";
<a asp-action="SetLanguage" asp-route-returnUrl="#returnUrl" asp-route-parameters="#parameters" asp-route-culture="nl-NL" asp-controller="Home">
<img src="~/css/Languages/nl.png" />
You can create a Variable Parameters and do the same thing you did with your returnUrl.
Then in your HomeController you can use both your returnUrl and Parameters.
[HttpGet]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
//If parameters is null, only return returnUrl, else return both
if (parameters != null)
{
var item = returnUrl + parameters;
return LocalRedirect(item);
}
else { var item = returnUrl;
return LocalRedirect(item);
}
Console.WriteLine("The new CultureInfo is now: " + CultureInfo.CurrentCulture);
return LocalRedirect(returnUrl);
}
this reference helped me understand how to use the Context.Request.Path combined with looking at my own code.

The fact using htmlAttributes causes URL troubles

I'm working on an asp .net project. I need to globalize it so I followed a tutorial (I'm a junior dev) on developpez.com (This one). I got a problem with the tutorial but after some searchs I resolve it. Currently, I got 2 links :
What I would like to do is to have flags instead of text. So, I want to add a class to the '< a >' element and put a background image. But when I add the class, the link generated by the Html.Helper is becoming strange. Some parameters are added, I don't understand why.
EDIT : I forgot to write that links doesn't change language when I add the class.
URLs before I add class to a link
English
[Français]
URLs after I add class to a link
English
<a class="drapeauFrance" href="/?Count=6&Keys=System.Collections.Generic.Dictionary%602%2BKeyCollection%5BSystem.String%2CSystem.Object%5D&Values=System.Collections.Generic.Dictionary%602%2BValueCollection%5BSystem.String%2CSystem.Object%5D">[Français]</a>
How I include links to my partial view
#using MIFA.Helpers
#Html.LanguageSelectorLink("en", "[English]", "English", null)
#Html.LanguageSelectorLink("fr", "[Français]", "Français", new { #class = "drapeauFrance" })
How the LangageSelectorLink works
public static MvcHtmlString LanguageSelectorLink(this HtmlHelper helper, string cultureName, string selectedText, string unselectedText, object htmlAttributes, string languageRouteName = "lang", bool strictSelected = false)
{
var language = helper.LanguageUrl(cultureName, languageRouteName, strictSelected);
var link = helper.RouteLink(language.IsSelected ? selectedText : unselectedText, "LocalizedDefault", language.RouteValues, htmlAttributes);
return link;
}
How LangageUrl works
public static Language LanguageUrl(this HtmlHelper helper, string cultureName, string languageRouteName = "lang", bool strictSelected = false)
{
cultureName = cultureName.ToLower();
var routeValues = new RouteValueDictionary(helper.ViewContext.RouteData.Values);
var queryString = helper.ViewContext.HttpContext.Request.QueryString;
foreach (string key in queryString)
{
if (queryString[key] != null && !string.IsNullOrWhiteSpace(key))
{
if (routeValues.ContainsKey(key))
{
routeValues[key] = queryString[key];
}
else
{
routeValues.Add(key, queryString[key]);
}
}
}
var actionName = routeValues["action"].ToString();
var controllerName = routeValues["controller"].ToString();
routeValues[languageRouteName] = cultureName;
var urlHelper = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);
var url = urlHelper.RouteUrl("LocalizedDefault", routeValues);
var current_lang_name = Thread.CurrentThread.CurrentUICulture.Name.ToLower();
var isSelected = strictSelected ? current_lang_name == cultureName : current_lang_name.StartsWith(cultureName);
return new Language()
{
Url = url,
ActionName = actionName,
ControllerName = controllerName,
RouteValues = routeValues,
IsSelected = isSelected
};
}
I'm asking your help to understand why the URL is changed when I add html attributes.
I tried to understand the function 'LanguageUrl' because to my mind the problem is here but my knowledge are too weak to understand it or to understand the htmlAttributes's impact on URL.
If I made some english mistakes, please tell me I will edit my question.
Thank you in advance for your answers.
It's a bit hard to explain but let me try. There is no such override of the Method RouteLink accepting a type RouteValueDictionary as the third and html attributes of type object. Instead, another override of the Method is assumed automatically, where third parameter is of type object:
RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, Object routeValues, Object htmlAttributes)
Now the effect is that RouteValueDictionary is used like an anonymous object, by reflecting it's Properties. Which is what you don't want.
One possible solution will be to convert the htmlAttributes to parameter, so that a different Method override is used:
RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues, IDictionary<string, Object> htmlAttributes)
To get this working, all you have to do is convert the anonymous object to IDictionary<string, Object> which you will do by calling:
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
Your new LanguageSelectorLinkMethod:
public static MvcHtmlString LanguageSelectorLink(this HtmlHelper helper, string cultureName, string selectedText, string unselectedText, object htmlAttributes, string languageRouteName = "lang", bool strictSelected = false)
{
var language = helper.LanguageUrl(cultureName, languageRouteName, strictSelected);
var attributeDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
var link = helper.RouteLink(language.IsSelected ? selectedText : unselectedText, "LocalizedDefault", language.RouteValues, attributeDictionary);
return link;
}
voilà!
I think your hitting the wrong overload, try:
var link = helper.RouteLink(language.IsSelected ? selectedText : unselectedText, language.RouteValues, htmlAttributes);

Menu in Site Master in MVC. How to highlight selected page [duplicate]

I need to highlight active link in the menu. My menu is in the master page by the way. I'm looking for the best way to implement this? Any ideas?
The best way to handle this is to write an HTML helper. You could use RouteData.Values["action"] to get the currently executing action and compare to the menu name and if they match apply a CSS class that will highlight it.
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string action,
string text
)
{
var menu = new TagBuilder("div");
var currentAction = (string)htmlHelper.ViewContext.RouteData.Values["action"];
if (string.Equals(
currentAction,
action,
StringComparison.CurrentCultureIgnoreCase)
)
{
menu.AddCssClass("highlight");
}
menu.SetInnerText(text);
return MvcHtmlString.Create(menu.ToString());
}
And then use this helper to render the menu items:
<%: Html.MenuItem("about", "About us") %>
<%: Html.MenuItem("contact", "Contact us") %>
...
I am always using this solution
Html Part
<ul>
#Html.ListItemAction("Home Page","Index","Home")
#Html.ListItemAction("Manage","Index","Home")
</ul>
Helper Part
public static class ActiveLinkHelper
{
public static MvcHtmlString ListItemAction(this HtmlHelper helper, string name, string actionName, string controllerName)
{
var currentControllerName = (string)helper.ViewContext.RouteData.Values["controller"];
var currentActionName = (string)helper.ViewContext.RouteData.Values["action"];
var sb = new StringBuilder();
sb.AppendFormat("<li{0}", (currentControllerName.Equals(controllerName, StringComparison.CurrentCultureIgnoreCase) &&
currentActionName.Equals(actionName, StringComparison.CurrentCultureIgnoreCase)
? " class=\"active\">" : ">"));
var url = new UrlHelper(HttpContext.Current.Request.RequestContext);
sb.AppendFormat("{1}", url.Action(actionName, controllerName), name);
sb.Append("</li>");
return new MvcHtmlString(sb.ToString());
}
}
I use this solution:
First - add attribute data-menuPageId to your menu links
<ul class="menu">
<li data-menuPageId="home">
#(Html.Link<HomeController>(x => x.Index(), "Home"))
</li>
<li data-menuPageId="products">
#(Html.Link<ProductsController>(x => x.Index(), "Products"))
</li>
</li>
Next - add hidden field for current page Id to Layout.cshtml
<input type="hidden" id="currentPageId" value="#(ViewBag.Page ?? "home")" />
Add ViewBag.Page initializtion at your pages like Home or Products
#{
ViewBag.Page = "products";
}
And last thing you should reference this javascipt from yor Layout.cshtml
$(function() {
var pageIdAttr = "data-menuPageId";
var currentPage = $("#currentPageId").attr("value");
var menu = $(".menu");
$("a[" + pageIdAttr + "]").removeClass("selected");
$("a[" + pageIdAttr + "=\"" + currentPage + "\"]", menu).addClass("selected");
});
In layout try like following:
#{
string url = Request.RawUrl;
}
#switch (url)
{
case "/home/":
#Html.ActionLink("Home", "../home/", null, new { #style = "background:#00bff3;" })
#Html.ActionLink("Members", "../home/MembersList/")
break;
case "/home/MembersList/":
#Html.ActionLink("Home", "../home/")
#Html.ActionLink("Members", "../home/MembersList/", null, new { #style = "background:#00bff3;" })
break;
default:
#Html.ActionLink("Home", "../home/")
#Html.ActionLink("Members", "../home/MembersList/")
break;
}
The simplest solution:
1) Connect jQuery to #RenderBody ()
2) On Layout
...
<li class="nav-item">
<a class="nav-link text-dark" id="navItemPortfolio" asp-area="" asp-controller="Home" asp-action="Portfolio">Portfolio</a>
</li>
...
3) Write something similar on your page (View)
<script>
$(function () {
$("#navItemPortfolio").addClass("active");
});
</script>
I'm pretty sure you can do this with pure CSS. It involves class selectors and faffing around with the body tag. I would go with the helper method every day of the week.

Edit the innerhtml of a <li> to add an <i> tag with icon image

I have created a custom helper for tracking the current menu item in my MVC application and would now like to replace the icons I was using before the change and set the relevant href. Previously my list items looked like this:
<li><i class="icon-lock"></i>Admin</li>
<li><a class="active" href="#Url.Action("Index", "Home")"><i class="icon-home"></i>Home</a></li>
I now have created the generic menu item by using the following code:
using System;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace MetaLearning.Helpers
{
public static class MenuExtensions
{
public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper,
string text, string action,
string controller,
string iconClass,
object routeValues = null,
object htmlAttributes = null)
{
var li = new TagBuilder("li");
var routeData = htmlHelper.ViewContext.RouteData;
var currentAction = routeData.GetRequiredString("action");
var currentController = routeData.GetRequiredString("controller");
li.InnerHtml = #"<a class=""active"" href=""/""><i class=""icon-home""></i>Home</a>";
if (currentController.Contains(text))
{
li.InnerHtml = #"<a class=""active"" href=""/""><i class=""icon-home""></i>"+ text + "</a>";
}
else
{
li.InnerHtml = #"<i class=""icon-home""></i>" + text + "";
}
return MvcHtmlString.Create(li.ToString());
}
}
}
How can I set the href to the "/controller" and the name of the icon to iconClass. I have tried the following:
li.InnerHtml = #"<a class=""active"" href=""/""><i class=""icon-home""></i>"+ text + "</a>";
With this I am successfully showing the correct text but when I try to replace the href or icon class names with the relevant variables my string formation is incorrect
Try not to concat strings with '+' operator this is not good behavior here you can read more yourself.
How can I set the href to the "/controller" and the name of the icon
to iconClass.
li.InnerHtml = string.Format("<i class=\"{2}\" /i>{1}", controller, iconClass, text);

How to visually indicate current page in ASP.NET MVC?

As a base for discussion. Create a standard ASP.NET MVC Web project.
It will contain two menu items in the master page:
<div id="menucontainer">
<ul id="menu">
<li>
<%= Html.ActionLink("Home", "Index", "Home")%></li>
<li>
<%= Html.ActionLink("About", "About", "Home")%></li>
</ul>
</div>
How can I set the visual CSS style indicating the current page.
For example, when in the About page/controller, I essentially would like to do this:
<%= Html.ActionLink("About", "About", "Home", new {class="current"})%></li>
And, of course, when on the home page:
<%= Html.ActionLink("Home", "Index", "Home", new {class="current"})%></li>
(Having a CSS style names current that visually indicates in the menu that this is the current page.)
I could break out the menu div from the master page into a content place holder, but that would mean that I must put the menu on every page.
Any ideas, is there a nice solution to this?
The easiest way is to get the current controller and action from the ViewContext's RouteData. Note the change in signature and use of # to escape the keyword.
<% var controller = ViewContext.RouteData.Values["controller"] as string ?? "Home";
var action = ViewContext.RouteData.Values["action"] as string ?? "Index";
var page = (controller + ":" + action).ToLower();
%>
<%= Html.ActionLink( "About", "About", "Home", null,
new { #class = page == "home:about" ? "current" : "" ) %>
<%= Html.ActionLink( "Home", "Index", "Home", null,
new { #class = page == "home:index" ? "current" : "" ) %>
Note that you could combine this an HtmlHelper extension like #Jon's and make it cleaner.
<%= Html.MenuLink( "About", "About", "Home", null, null, "current" ) %>
Where MenuActionLink is
public static class MenuHelperExtensions
{
public static string MenuLink( this HtmlHelper helper,
string text,
string action,
string controller,
object routeValues,
object htmlAttributes,
string currentClass )
{
RouteValueDictionary attributes = new RouteValueDictionary( htmlAttributes );
string currentController = helper.ViewContext.RouteData.Values["controller"] as string ?? "home";
string currentAction = helper.ViewContext.RouteData.Values["action"] as string ?? "index";
string page = string.Format( "{0}:{1}", currentController, currentAction ).ToLower();
string thisPage = string.Format( "{0}:{1}", controller, action ).ToLower();
attributes["class"] = (page == thisPage) ? currentClass : "";
return helper.ActionLink( text, action, controller, new RouteValueDictionary( routeValues ), attributes );
}
}
I recently created an HTML Helper for this that looks like:
public static string NavigationLink(this HtmlHelper helper, string path, string text)
{
string cssClass = String.Empty;
if (HttpContext.Current.Request.Path.IndexOf(path) != -1)
{
cssClass = "class = 'selected'";
}
return String.Format(#"<li><a href='{0}' {1}>{2}</a></li>", path, cssClass, text);
}
The Implementation looks like this:
<ul id="Navigation">
<%=Html.NavigationLink("/Path1", "Text1")%>
<%=Html.NavigationLink("/Path2", "Text2")%>
<%=Html.NavigationLink("/Path3", "Text3")%>
<%=Html.NavigationLink("/Path4", "Text4")%>
</ul>
If you are using T4MVC, you can use this:
public static HtmlString MenuLink(
this HtmlHelper helper,
string text,
IT4MVCActionResult action,
object htmlAttributes = null)
{
var currentController = helper.ViewContext.RouteData.Values["controller"] as string ?? "home";
var currentAction = helper.ViewContext.RouteData.Values["action"] as string ?? "index";
var attributes = new RouteValueDictionary(htmlAttributes);
var cssClass = (attributes.ContainsKey("class"))
? attributes["class"] + " "
: string.Empty;
string selectedClass;
if(action.Controller.Equals(currentController, StringComparison.InvariantCultureIgnoreCase)
{
selectedClass = "selected-parent";
if(action.Action.Equals(currentAction, StringComparison.InvariantCultureIgnoreCase))
selectedClass = "selected";
}
cssClass += selectedClass;
attributes["class"] = cssClass;
return helper.ActionLink(text, (ActionResult)action, attributes);
}
It might just be that it's the 5th parameter, so slot a null before your html attribute. This post here describes it as such, though you can pass in some stuff on the 4th arguement, the 5th is specifically for HTMLattributes
<script type="javascript/text">
$( document ).ready( function() {
#if (Request.Url.AbsolutePath.ToLower() == "/")
{
#Html.Raw("$('.navbar-nav li').eq(0).attr('class','active');")
}
#if (Request.Url.AbsolutePath.ToLower().Contains("details"))
{
#Html.Raw("$('.navbar-nav li').eq(1).attr('class','active');")
}
#if (Request.Url.AbsolutePath.ToLower().Contains("schedule"))
{
#Html.Raw("$('.navbar-nav li').eq(2).attr('class','active');")
}
});
</script>
Chucked this together in 5mins, I could probably refactor it, but should give you the basic idea, its probably most useful for smaller sites.

Resources