I'm using an extension method to maintain a css class on active links on the menu.
However I've got an issue where the htmlAttributes and the object values are causing errors.
I have the below in my Razor page but I don't understand how I'm meant to be parsing the htmlAttributes.
@Html.MenuLink("Summary", "Summary", "Graphs", null, new { @class = "dropdown-toggle caret", data_target = "#", data_toggle = "dropdown" })
From looking at the HtmlHelper the method should have IDictionary<object, string>
as the type for the htmlAttributes. The new { @class = "dropdown-toggle caret", data_target = "#", data_toggle = "dropdown" }
syntax isn't typical for dictionaries so is this correct?
Obviously I'm doing something wrong as it's returning the below error:
Argument 6: cannot convert from '<anonymous type: string class, string data_target, string data_toggle>' to 'System.Collections.Generic.IDictionary<object, string>'
Extension method I'm trying to get working below:
public static MvcHtmlString MenuLink(this HtmlHelper htmlHelper, string text, string action, string controller, RouteValueDictionary routeValues, IDictionary<object, string> htmlAttributes)
{
var routeData = htmlHelper.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 htmlHelper.ActionLink(text, action, controller, null, new { @class = "currentMenu" });
}
return htmlHelper.ActionLink(text, action, controller);
}
Change the parameter from IDictionary<object, string> htmlAttributes
to object htmlAttributes
since your passing the attributes as an object.
You can then convert the object using
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
However, no where in your extension method do you ever use the attributes. All your generating is class = "currentMenu"
depending on the current controller and action names. If you intention is to add the attributes plus the class name (depending on the condition), you can then use
attributes.Add("class", "currentMenu");
Your complete method to allow defining both route values and html attributes, and to conditionally include the "currentMenu"
class name should be
public static MvcHtmlString MenuLink(this HtmlHelper htmlHelper, string text, string action, string controller, object routeValues, object htmlAttributes)
{
var routeData = htmlHelper.ViewContext.RouteData.Values;
string currentController = (string)routeData["controller"];
string currentAction = (string)routeData["action"];
if (string.Equals(action, currentAction, StringComparison.OrdinalIgnoreCase) && string.Equals(controller, currentController, StringComparison.OrdinalIgnoreCase))
{
if (htmlAttributes == null)
{
return htmlHelper.ActionLink(text, action, controller, routeValues, new { @class = "currentMenu" });
}
else
{
// convert object to RouteValueDictionary
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (attributes.ContainsKey("class"))
{
// append the class name
attributes["class"] = string.Format("{0} currentMenu", attributes["class"]);
}
else
{
// add the class name
attributes.Add("class", "currentMenu");
}
return htmlHelper.ActionLink(text, action, controller, new RouteValueDictionary(routeValues), attributes);
}
}
return htmlHelper.ActionLink(text, action, controller, routeValues, htmlAttributes);
}
Side note: You should also consider including other overloads to accept RouteValueDictionary routeValues
and IDictionary<String, Object>) htmlAttributes
as per the in-built ActionLink()
methods and you can inspect the source code to see how the various overloads fall through to the other overloads.