On the forums, someone was asking about creating a horizontal menu using Razor code in ASP.NET Web Pages. The technique of combining an unordered list (<ul>
element) with CSS to create a horizontal layout, using <a>
elements as the menu items, is a pretty well-known one; you can find examples here and here, not to mention in the Site.css file that comes with a lot of the Visual Studio web project templates.
The actual Razor part is likewise not hard. Someone showed this code as a way to dynamically build a list (to which you'd then apply the CSS):
<ul>
@foreach (var item in collection)
{
<li>@item</li>
}
</ul>
The interesting part was a comment that the original poster made: It sounds like if I have lots of logic inside the loop, I might be best served moving that to the back end somewhere and just calling a method that returns the text to display within each list item.
What this sounded like to me was a fun excuse to see about creating a custom helper. Helpers are, in effect, chunks of code and markup; when you invoke the helper, it runs the code and emits the markup. For example, by creating a helper to render a horizontal menu, you could do something like this in a page:
@MyHelpers.HorizontalMenu(
"Home|Default.cshtml",
"About|About.cshtml",
"Login|Login.cshtml",
"Register|Register.cshtml")
<h1>My Page</h1>
<p>Hello, horizontal menu.</p>
And get something like this:
Well, anyway, in the App_Code folder of an ASP.NET Web Pages site (it must be in that folder), I created a file named MyHelpers.cshtml. In the new file I replaced the default contents with the following:
@helper HorizontalMenu(params string[] menuListArray) {
string[] menuItem;
string menulink = "";
string menuLIitems = "";
<style>
/* This should all be in a .css somewhere */
#horizontalMenu{height:28px;background-color:lightgray;
font-family:"Segoe UI";font-size:11pt;font-weight:bold;
color:white;}
#horizontalMenu ul {margin:0px; padding:0px;}
#horizontalMenu ul li{
display:inline;height:30px;float:left;list-style:none;
margin-left:15px;position:relative;
border-right:2px solid white;padding:6px;
}
#horizontalMenu li a {color:#fff; text-decoration:none;}
#horizontalMenu li a:hover {color:red;}
</style>
<div id="horizontalMenu">
@for(int i = 0; i < menuListArray.Length; i++){
menulink = "";
if(menuListArray[i].Contains("|")){
menuItem = menuListArray[i].Split( new Char[]{'|'});
menulink =
String.Format("<a href=\"{1}\">{0}</a>",
menuItem[0],
menuItem[1]);
}
else{
menulink = menuListArray[i];
}
menuLIitems += "<li>" + menulink + "</li>";
}
<ul>@Html.Raw(menuLIitems)</ul>
</div>
}
Update 7 Feb 2012 Fixed a typo in the code (I'd named the variable menuListArrary
instead of menuListArray
). I'd used the misspelled variable name consistently, so everything worked ok, but I corrected the name anyway to avoid potential confusion.
Some things of note, I guess:- The name of the file (MyHelpers.cshtml) acts as the namespace for any helpers in that file; the name of the helper itself (first list of the previous example) is the method. That's why the earlier example invokes the helper as
MyHelpers.HorizontalMenu
.
- The CSS for the menu layout probably should not be in this file; it probably should be in a separate .css file.
- And/or at least parts of the CSS should probably be parameterized so that you can pass values to it when you invoke the menu. Maybe.
- You can pass as many items to this as you like (hence the use of the
params string[]
syntax in the helper method signature).
- The original poster had noted that in some browsers, if the
<ul>
and <li>
elements are on separate lines, the silly browser puts blank spaces between them. So a goal here was to smush everything together onto one line.
- The menu text and menu target/link are separated by a pipe character ("|"). This was a quick-and-dirty thing. It might be more elegant to pass, say, 2-element arrays to the helper or something, but I generally think that invoking helpers in a page should be as straightforward as possible.
- As coded, the links for the menu items have to be absolute. However, they really should be expressed, or at least expressable, using the ASP.NET
~
operator, which returns the virtual root, but which requires the ASP.NET Href
method to resolve. I didn't bother.
- For some reason I thought it was important to have logic in case a menu item was passed to the helper that didn't have a target in it. I can't offhand think of when this would ever be useful, tho.
I'm sure if I gave this another 5 minutes of thought I could improve it. And others could probably give it 10 seconds of thought, ditto. But it was fun while it lasted. :-)