When I was working on the TalentOn project (Promotion in MSDN Chinese) I was asked to implement a functionality that makes the top menu items highlighted when the currently viewing page was in that section. This might be a common scenario in the web application development I think. Simple Example When thinking about the solution of the highlighted menu items the biggest problem would be how to define the sections (menu item) and the pages it belongs to rather than making the menu highlighted. With the ASP.NET MVC framework we can use the controller – action infrastructure for us to achieve it. Each controllers would have a related menu item on the master page normally. The menu item would be highlighted if any of the views under this controller are being shown. Some specific menu items would be highlighted of that action was invoked, for example the home page, the about page, etc. The check rule can be specified on-demand. For example I can define the action LogOn and Register of Account controller should make the Account menu item highlighted while the ChangePassword should make the Profile menu item highlighted. I’m going to use the HtmlHelper to render the highlight-able menu item. The key point is that I need to pass the predication to check whether the current view belongs to this menu item which means this menu item should be highlighted or not. Hence I need a delegate as its parameter. The simplest code would be like this. 1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using System.Web.Mvc;
6: using System.Web.Mvc.Html;
7:
8: namespace ShaunXu.Blogs.HighlighMenuItem
9: {
10: public static class HighlightMenuItemHelper
11: {
12: public static MvcHtmlString HighlightMenuItem(this HtmlHelper helper,
13: string text, string controllerName, string actionName, object routeData, object htmlAttributes,
14: string highlightText, object highlightHtmlAttributes,
15: Func<HtmlHelper, bool> highlightPredicate)
16: {
17: var shouldHighlight = highlightPredicate.Invoke(helper);
18: if (shouldHighlight)
19: {
20: return helper.ActionLink(string.IsNullOrWhiteSpace(highlightText) ? text : highlightText,
21: actionName, controllerName, routeData, highlightHtmlAttributes == null ? htmlAttributes : highlightHtmlAttributes);
22: }
23: else
24: {
25: return helper.ActionLink(text, actionName, controllerName, routeData, htmlAttributes);
26: }
27: }
28: }
29: }
There are 3 groups of the parameters: the first group would be the same as the in-build ActionLink method parameters. It has the link text, controller name and action name, etc passed in so that I can render a valid linkage for the menu item. The second group would be more focus on the highlight link text and Html attributes. I will use them to render the highlight menu item. The third group, which contains one parameter, would be a predicate that tells me whether this menu item should be highlighted or not based on the user’s definition.
And then I changed my master page of the sample MVC application. I let the Home and About menu highlighted only when the Index and About action are invoked. And I added a new menu named Account which should be highlighted for all actions/views under its Account controller. So my master would be like this.
1: <div id="menucontainer">
2:
3: <ul id="menu">
4: <li><% 1: : Html.HighlightMenuItem( 2: "Home", "Home", "Index", null, null, 3: "[Home]", null, 4: helper => helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" 5: && helper.ViewContext.RouteData.Values["action"].ToString() == "Index")%></li>
5:
6: <li><% 1: : Html.HighlightMenuItem( 2: "About", "Home", "About", null, null, 3: "[About]", null, 4: helper => helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" 5: && helper.ViewContext.RouteData.Values["action"].ToString() == "About")%></li>
7:
8: <li><% 1: : Html.HighlightMenuItem( 2: "Account", "Account", "LogOn", null, null, 3: "[Account]", null, 4: helper => helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")%></li>
9:
10: </ul>
11:
12: </div>
Note: You need to add the import section for the namespace “ShaunXu.Blogs.HighlighMenuItem” to make the extension method I created below available.
So let’s see the result.
When the home page was shown the Home menu was highlighted since at this moment it was controller = Home and action = Index.
And if I clicked the About menu you can see it turned highlighted as now the action was About.
And if I navigated to the register page the Account menu was highlighted since it should be like that when any actions under the Account controller was invoked.
Fluently Language
Till now it’s a fully example for the highlight menu item but not perfect yet. Since the most common scenario would be: highlighted when the action invoked, or highlighted when any action was invoked under this controller, we can created 2 shortcut method so for them so that normally the developer will be no need to specify the delegation.
Another place we can improve would be, to make the method more user-friendly, or I should say developer-friendly. As you can see when we want to add a highlight menu item we need to specify 8 parameters and we need to remember what they mean. In fact we can make the method more “fluently” so that the developer can have the hints when using it by the Visual Studio IntelliSense. Below is the full code for it.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using System.Web.Mvc;
6: using System.Web.Mvc.Html;
7:
8: namespace Ethos.Xrm.HR
9: {
10: #region Helper
11:
12: public static class HighlightActionMenuHelper
13: {
14: public static IHighlightActionMenuProviderAfterCreated HighlightActionMenu(this HtmlHelper helper)
15: {
16: return new HighlightActionMenuProvider(helper);
17: }
18: }
19:
20: #endregion
21:
22: #region Interfaces
23:
24: public interface IHighlightActionMenuProviderAfterCreated
25: {
26: IHighlightActionMenuProviderAfterOn On(string actionName, string controllerName);
27: }
28:
29: public interface IHighlightActionMenuProviderAfterOn
30: {
31: IHighlightActionMenuProviderAfterWith With(string text, object routeData, object htmlAttributes);
32: }
33:
34: public interface IHighlightActionMenuProviderAfterWith
35: {
36: IHighlightActionMenuProviderAfterHighlightWhen HighlightWhen(Func<HtmlHelper, bool> predicate);
37: IHighlightActionMenuProviderAfterHighlightWhen HighlightWhenControllerMatch();
38: IHighlightActionMenuProviderAfterHighlightWhen HighlightWhenControllerAndActionMatch();
39: }
40:
41: public interface IHighlightActionMenuProviderAfterHighlightWhen
42: {
43: IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(object highlightHtmlAttributes, string highlightText);
44: IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(object highlightHtmlAttributes);
45: IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(string cssClass, string highlightText);
46: IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(string cssClass);
47: }
48:
49: public interface IHighlightActionMenuProviderAfterApplyHighlightStyle
50: {
51: MvcHtmlString ToActionLink();
52: }
53:
54: #endregion
55:
56: public class HighlightActionMenuProvider :
57: IHighlightActionMenuProviderAfterCreated,
58: IHighlightActionMenuProviderAfterOn, IHighlightActionMenuProviderAfterWith,
59: IHighlightActionMenuProviderAfterHighlightWhen, IHighlightActionMenuProviderAfterApplyHighlightStyle
60: {
61: private HtmlHelper _helper;
62:
63: private string _controllerName;
64: private string _actionName;
65: private string _text;
66: private object _routeData;
67: private object _htmlAttributes;
68:
69: private Func<HtmlHelper, bool> _highlightPredicate;
70:
71: private string _highlightText;
72: private object _highlightHtmlAttributes;
73:
74: public HighlightActionMenuProvider(HtmlHelper helper)
75: {
76: _helper = helper;
77: }
78:
79: public IHighlightActionMenuProviderAfterOn On(string actionName, string controllerName)
80: {
81: _actionName = actionName;
82: _controllerName = controllerName;
83: return this;
84: }
85:
86: public IHighlightActionMenuProviderAfterWith With(string text, object routeData, object htmlAttributes)
87: {
88: _text = text;
89: _routeData = routeData;
90: _htmlAttributes = htmlAttributes;
91: return this;
92: }
93:
94: public IHighlightActionMenuProviderAfterHighlightWhen HighlightWhen(Func<HtmlHelper, bool> predicate)
95: {
96: _highlightPredicate = predicate;
97: return this;
98: }
99:
100: public IHighlightActionMenuProviderAfterHighlightWhen HighlightWhenControllerMatch()
101: {
102: return HighlightWhen((helper) =>
103: {
104: return helper.ViewContext.RouteData.Values["controller"].ToString().ToLower() == _controllerName.ToLower();
105: });
106: }
107:
108: public IHighlightActionMenuProviderAfterHighlightWhen HighlightWhenControllerAndActionMatch()
109: {
110: return HighlightWhen((helper) =>
111: {
112: return helper.ViewContext.RouteData.Values["controller"].ToString().ToLower() == _controllerName.ToLower() &&
113: helper.ViewContext.RouteData.Values["action"].ToString().ToLower() == _actionName.ToLower();
114: });
115: }
116:
117: public IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(object highlightHtmlAttributes, string highlightText)
118: {
119: _highlightText = highlightText;
120: _highlightHtmlAttributes = highlightHtmlAttributes;
121: return this;
122: }
123:
124: public IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(object highlightHtmlAttributes)
125: {
126: return ApplyHighlighStyle(highlightHtmlAttributes, _text);
127: }
128:
129: public IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(string cssClass, string highlightText)
130: {
131: return ApplyHighlighStyle(new { @class = cssClass }, highlightText);
132: }
133:
134: public IHighlightActionMenuProviderAfterApplyHighlightStyle ApplyHighlighStyle(string cssClass)
135: {
136: return ApplyHighlighStyle(new { @class = cssClass }, _text);
137: }
138:
139: public MvcHtmlString ToActionLink()
140: {
141: if (_highlightPredicate.Invoke(_helper))
142: {
143: // should be highlight
144: return _helper.ActionLink(_highlightText, _actionName, _controllerName, _routeData, _highlightHtmlAttributes);
145: }
146: else
147: {
148: // should not be highlight
149: return _helper.ActionLink(_text, _actionName, _controllerName, _routeData, _htmlAttributes);
150: }
151: }
152: }
153: }
So in the master page when I need the highlight menu item I can “tell” the helper how it should be, just like this.
1: <li>
2: <% 1: : Html.HighlightActionMenu() 2: .On("Index", "Home") 3: .With(SiteMasterStrings.Home, null, null) 4: .HighlightWhenControllerMatch() 5: .ApplyHighlighStyle(new { style = "background:url(../../Content/Images/topmenu_bg.gif) repeat-x;text-decoration:none;color:#feffff;" }) 6: .ToActionLink() %>
3: </li>
While I’m typing the code the IntelliSense will advise me that I need a highlight action menu, on the Index action of the Home controller, with the “Home” as its link text and no need the additional route data and Html attributes, and it should be highlighted when the controller was “Home”, and if it’s highlighted the style should be like this and finally render it to me.
This is something we call “Fluently Language”. If you had been using Moq you will see that’s very development-friendly, document-ly and easy to read.
Summary
In this post I demonstrated how to implement a highlight menu item in ASP.NET MVC by using its controller – action infrastructure. We can see the ASP.NET MVC helps us to organize our web application better. And then I also told a little bit more on the “Fluently Language” and showed how it will make our code better and easy to be used.
Hope this helps,
Shaun
All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.