Saturday, May 23, 2009 11:23 AM
Tanzim Saqib
Building a basic Menu control for ASP.NET MVC
There are two different ways to build controls for ASP.NET MVC as of now. The most common way is by HTML Helper extension methods. You will find such methods being used in numerous places inside Views. Such methods can take any complexity of parameters, yet return only standard HTML tags in string format as response. Our Menu control will render a basic structure of CSS Menu, which appearance can be controlled from the site’s CSS file. So no matter which look & feel you would like to see your menu reflects, you do not have to change your Menu control. You will only have to make changes into your CSS.
The following TextBox method renders an input tag with name “username”:
<%= Html.TextBox("username") %>
This sort of extension methods often become handy when you need to render complex or reusable controls in different Views. Today I will talk about a simple Menu control I built which renders UL/LI tags, but can take parameters ranging from string to complex types such as MvcMenuItem. Let us see an example how we would like to see Menu control to be used:
1: <%
2: var list = new List<MvcMenuItem>();
3:
4: list.Add(new MvcMenuItem{ Text = "Home", ActionName = "Index" });
5: list.Add(new MvcMenuItem("About", "About", "Home"));
6: list.Add(new MvcMenuItem("Feedback", "alert('feedback');"));
7: %>
8:
9: <%= Html.Menu()
10: .ClientId("menu")
11: .AddRange(list)
12: .HtmlAttributes(new { style="color: Red" })
13: .Render()
14: %>
From the snippet you can see, the Menu control is taking a list of MvcMenuItem class which we will create later, is being invoked from Html class and supports method calls in chain which is known as Fluent Interface. Let us take a look at how many different ways we can add menu item to the Menu control:
MvcMenu Add(MvcMenuItem item)
MvcMenu Add(string text, string actionName, string controllerName, string clientCallbackMethod)
MvcMenu Add(string text, string actionName, string controllerName)
MvcMenu Add(string text, string clientCallbackMethod)
MvcMenu Add(string text)
MvcMenu AddRange(List<MvcMenuItem> items)
I am sure you noticed the return type in every method mentioned above. It is because we have to return the current instance of the class from within every method to support Fluent Interface. Let us also take a look at the class properties of MvcMenuItem which has several constructor overloads, helpful when initializing instances. You will notince ClientCallbackMethod property which indicates if this menu item is meant to be calling a client side method. You pass a JavaScript method/code block the menu item will execute it upon user’s click.
public class MvcMenuItem
{
public string Text { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string ClientCallbackMethod { get; set; }
}
HtmlHelper resides in a different assembly than we are building MVC control for. So how would we write extension for it? All we have to do is, reference the System.Web.Mvc assembly and we will write the class inside that namespace too:
namespace System.Web.Mvc
{
public static class MvcMenuExtensions
{
public static MvcMenu Menu(this HtmlHelper helper)
{
return new MvcMenu(helper);
}
}
}
As we are writing extension method, you will notice we followed the convention of putting it in a static class, and the static method named Menu is taking the HtmlHelper which we will pass to the MvcMenu we will be building. Constructor of the MvcMenu class is important. It takes that HtmlHelper and store it to its private variable so that it can be reused later. We need it to be reused later since we are supporting Fluent Interface.
public MvcMenu(HtmlHelper helper)
{
Helper = helper;
Items = new List<MvcMenuItem>();
}
As you can understand MvcMenu is the class we are writing responsible for processing menu items, and rendering. Next part is easy and straight forward. We will implement Render method which will write Menu Items for us:
1: public MvcMenu HtmlAttributes(object dictionary)
2: {
3: HtmlProperties = new RouteValueDictionary(dictionary);
4: return this;
5: }
6:
7: public string Render()
8: {
9: var ulTag = new TagBuilder("ul");
10: ulTag.MergeAttribute("id", Id ?? string.Empty);
11: ulTag.MergeAttributes(HtmlProperties);
12:
13: foreach (var item in Items)
14: {
15: var liTag = new TagBuilder("li");
16:
17: if(!string.IsNullOrEmpty(item.ClientCallbackMethod))
18: liTag.InnerHtml = string.Format("<a href=\"javascript:;\" onclick=\"{1}\">{0}</a>", item.Text, item.ClientCallbackMethod);
19: else
20: liTag.InnerHtml = Html.LinkExtensions.ActionLink(Helper, item.Text, item.ActionName, item.ControllerName ?? string.Empty);
21:
22: ulTag.InnerHtml += liTag.ToString();
23: }
24:
25: return ulTag.ToString();
26: }
We used the ActionLink method which helps us generate valid URL from Action and Controller name. We will reach up to that point only if there is no ClientCallbackMethod is defined. You will also notice the TagBuilder class which is can render a standard HTML tag for you even with specified style. Also do not forget to add the namespace in web.config:
<namespaces>
<add namespace="TanzimSaqib.Mvc.Menu"/>
...
</namespaces>
Download the code. Enjoy.
Filed under: ASP.NET, C#, Look and Feel, MVC