Search code examples
asp.net-mvcrazorhtml-helper

htmlAttributes in ActionLink Extension method MVC5


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

Solution

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