Razor Migration Notes 1: Moving a SitemapPath Control to ASP.NET Web Pages
After many years I decided that it is time to rewrite my Web site using Razor. A bit of history, I started it around 2003 using ASP.NET 1.1. When .NET 2.0 came around in 2005 I migrated to it and it was great being able to leverage features like MasterPages, Themes, Sitemaps, and many other features. Honestly it is a pretty simple Web site, with mostly content, so very few controls, Sitemap, my own custom Menu control, and a couple more. Last week it was moved to use .NET 4.0 and it feels its about time to go back and update it a bit, both in look and features. So this (if time permits) will be the first of a series of migration notes that I discover as I move it to use ASP.NET Razor (aka WebPages). Do note that this is not meant to be a best practice in anyway, I would never claim I can make such a thing, these will be only my personal notes as I discover more details in ASP.NET WebPages features and as I move my own implementation to use them.
So with that, one of the first things I faced during this migration, was the use of a Sitemap control (asp:SiteMapPath) in my MasterPage (future post about moving from MasterPages coming). I knew about Sitemap API, so I just decided to write a simple Sitemap helper that I can now use anywhere in Razor. The code is pretty simple, it basically generates an unordered list of links using <ul> and <li> with <a> inside, and used CSS to layout them in a way that I liked.
SitemapPath Control in WebForms
The original code I was using in my MasterPage looked like the following:
<asp:SiteMapPath CssClass="HeaderText" runat="server" ID="siteMap" ShowToolTips="true" NodeStyle-ForeColor="White" CurrentNodeStyle-Font-Bold="true" />
And generated the following markup:
<span id="siteMap" class="HeaderText"><a href="#siteMap_SkipLink"><img alt="Skip Navigation Links" height="0" width="0" src="https://blogs.msdn.com/WebResource.axd?d=S2jbW9E-HYlS0UQoRCcsm94KUJelFI6yS-CQIkFvzT6fyMF-zCI4oIF9bSrGjIv4IvVLF9liJbz7Om3voRpNZ8yQbW3z1KfqYr4e-0YYpXE1&t=634219272564138624" style='border-width:0px;' /></a><span><a title='Home' href='/' style='color:White;'>Home</a></span><span> > </span><span><a title='Free tools for download' href='/Tools/' style='color:White;'>Tools</a></span><span> > </span><span style='color:White;font-weight:bold;'>Code Translator</span><a id='siteMap_SkipLink'></a></span>
Which looks like the following in the browser:
I used some CSS to set the color, and background and other stuff, but still to set the last item to bold required me to use a property in the Sitemap to get it to look the way I wanted.
My Sitemap Helper in Razor
Since I was familiar with the Sitemap API and my goal was to change as “little” as possible as part of this first migration, I decided to write a Sitemap helper that I can use in my Layout pages. The code in the Page is as simple as it gets, you just call @Helpers.Sitemap() and that’s it (added the Div below to get some context in the markup, but that was already there with the SitemapPath control anyway):
<div class="bannerPath">
@Helpers.Sitemap()
</div>
This new helper version generates the markup below. I don’t know about you, but I can sure make more sense of what it says, and I imagine Search Engines will as well, I decided to use more semantically correct markup using a <nav> to signal navigational section and use a list of links.
<nav>
<ul class="siteMap">
<li><a href="https://blogs.msdn.com/" title="Home">Home</a> > </li>
<li><a href="https://blogs.msdn.com/Tools/" title="Free tools for download">Tools</a> > </li>
<li><span>Code Translator</span></li>
</ul>
</nav>
And it looks like the following in the browser (I decided to remove the underlining, and have more padding, and a new font, but all of that is CSS):
The Sitemap helper code
The code to do the Sitemap was pretty simple, just use the SiteMap API to get the current node. Since I’m picky and I wanted to generate the markup in the “right” order (note you could use CSS to float them to the right instead), I used a Stack to push the nodes while traversing them up. Finally just generate the <li>.
@helper Sitemap()
{
SiteMapNode currentNode = SiteMap.CurrentNode;
<nav>
<ul class="siteMap">
@if (currentNode != null)
{
// Push into a stack to reverse them
var node = currentNode;
var nodes = new Stack<SiteMapNode>();
while (node.ParentNode != null)
{
nodes.Push(node.ParentNode);
node = node.ParentNode;
}
while(nodes.Count != 0)
{
SiteMapNode n = nodes.Pop();
<li><a href="@n.Url" title="@n.Description">@n.Title</a> > </li>
}
<li><span>@currentNode.Title</span></li>
}
else
{
<li><span>@Page.Title</span></li>
}
</ul>
</nav>
}
To make it look the way I wanted I used the following CSS:
.siteMap
{ float:right; font-size:11px; color:White; display:inline; margin-top:3px; margin-bottom:3px; margin-left:0px; margin-right:10px; } .siteMap li,span { float:left; list-style-type:none; padding-left:5px; border-width:0px;} .siteMap span { font-weight:bold; } .siteMap a,a.Visited { color:White; text-decoration:none; }
Conclusion
SitemapPath control gives you a really easy way to put together a navigation control based on the Sitemap APIs (and the Web.Sitemap file in my case). Creating a simple ASP.NET Razor helper is actually pretty easy since all the functionality needed is there in the base API’s and although it required some code (20 lines of code) now I feel like I have more control over my markup, can style it in anyway I want using CSS and have cleaner markup rendered.
I’m sure there are better ways to do this, but as I said, the goal of this first pass is to push my site soon with as little changes possible while keeping the same functionality first.
Comments
- Anonymous
January 10, 2012
Where is the bannerPath css? - Anonymous
January 11, 2012
Here are the css classes I have:.bannerPath{ height:23px; background: url( "bar2.bmp" ); display:block;}.siteMap{ float:right; font-size:11px; color:White; display:inline; margin-top:3px; margin-bottom:3px; margin-left:0px; margin-right:10px;}.siteMap li, .siteMap span{ float:left; list-style-type:none; padding-left:5px; border-width:0px;}.siteMap span { font-weight:bold; }.siteMap a, .siteMap a:Visited {border:1px solid transparent; padding:2px; color:White; text-decoration:none; }.siteMap a:Hover { border:1px solid #6A6761; padding:2px; background-color:#A1A39E; border-radius:3px; }