Introduction
This articles provides an overview on how to do customized site navigation on MOSS publishing sites. If you are wondering whether that is configurable using the Site Settings page, read on.
Background
SharePoint 2007 comes with web content management features and its own custom SiteMapProvider
. Out of the box, any sites and sub-sites created under a publishing site will have its URL nailed tp the top navigation menu. While this is desirable in a typical WCM environment and is certainly configurable from the built-in Site Settings functionality, when used in a non-WCM environment, it can be problematic.
Suppose you want a fixed top navigation menu that is not associated to your site topology, then the out of the box provider cannot be used.
You may ask why I would use the publishing site in the first place. Well, most of the SharePoint custom site definitions require you to use a definition that's based from the publishing one. In order to get user-swappable master pages and alternate CSS files, you have to start with the publishing site.
By default, the following section exists in the web.config file for a publishing site. As you can see, CurrentNavSiteMapProvider
defines the provider used in a Publishing site.
<siteMap defaultProvider="CurrentNavSiteMapProvider" enabled="true">
<providers>
<add name="SPNavigationProvider" type="Microsoft.SharePoint.Navigation.SPNavigationProvider, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SPSiteMapProvider" type="Microsoft.SharePoint.Navigation.SPSiteMapProvider, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SPContentMapProvider" type="Microsoft.SharePoint.Navigation.SPContentMapProvider, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SPXmlContentMapProvider" siteMapFile="_app_bin/layouts.sitemap" type="Microsoft.SharePoint.Navigation.SPXmlContentMapProvider, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="AdministrationQuickLaunchProvider" description="QuickLaunch navigation provider for the central administration site" type="Microsoft.Office.Server.Web.AdministrationQuickLaunchProvider, Microsoft.Office.Server.UI, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SharedServicesQuickLaunchProvider" description="QuickLaunch navigation provider for shared services administration sites" type="Microsoft.Office.Server.Web.SharedServicesQuickLaunchProvider, Microsoft.Office.Server.UI, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="GlobalNavSiteMapProvider" description="CMS provider for Global navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Global" EncodeOutput="true" IncludePages="PerWeb" IncludeHeadings="true" IncludeAuthoredLinks="true" />
<add name="CombinedNavSiteMapProvider" description="CMS provider for Combined navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" EncodeOutput="true" IncludePages="PerWeb" IncludeHeadings="true" IncludeAuthoredLinks="true" />
<add name="CurrentNavSiteMapProvider" description="CMS provider for Current navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="true" IncludePages="PerWeb" IncludeHeadings="true" IncludeAuthoredLinks="true" />
<add name="CurrentNavSiteMapProviderNoEncode" description="CMS provider for Current navigation, no encoding of output" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="false" IncludePages="PerWeb" IncludeHeadings="true" IncludeAuthoredLinks="true" />
<add name="SiteDirectoryCategoryProvider" description="Site Directory category provider" type="Microsoft.SharePoint.Portal.WebControls.SiteDirectoryCategoryProvider, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="MySiteMapProvider" description="MySite provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteMapProvider, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="MySiteLeftNavProvider" description="MySite Left Nav provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteLeftNavProvider, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="UsagePagesSiteMapProvider" description="Provider for navigation in Portal Usage pages" type="Microsoft.SharePoint.Portal.Analytics.UsagePagesSiteMapProvider, Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<add name="SPXmlAdminContentMapProvider" siteMapFile="_app_bin/admin.sitemap" type="Microsoft.SharePoint.Navigation.SPXmlContentMapProvider, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
</providers>
</siteMap>
Correspondingly, the actual control that spits out navigation control is placed in the master page as follows. This is the custom control that provides the ability to show/hide sub-sites and etc in the SharePoint Site Settings page.
<publishingnavigation:portalsitemapdatasource id="SiteMapDataSourceRoot" runat="server"
sitemapprovider="CombinedNavSiteMapProvider" enableviewstate="true"
startfromcurrentnode="true" startingnodeoffset="0" showstartingnode="true">
</publishingnavigation:portalsitemapdatasource>
To override this behavior, you will need to do a few things.
- Create a new master page based on the
BlackBand.master
(or any master page in the directory under the PublishingLayouts feature). - Replace the
PublishingNavigation
control declaration with a menu control. - Create the code-behind file for the master page to load the custom navigation.
- Add the code-behind information to the master page directive and deploy the new master page to SharePoint plus the assemblies.
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
Dim menu As Menu = DirectCast(FindControl("Menu1"), Menu)
Dim doc As XDocument = XDocument.Load(
"C:\Inetpub\wwwroot\wss\VirtualDirectories\80\web.sitemap")
Dim query = From c In doc.Element("menuitems").Elements("menuitem")
Dim parentItem As MenuItem
For Each element As XElement In query
parentItem = New MenuItem()
Dim subQuery = From c In element.Elements("menuitem")
For Each node As XElement In subQuery
Dim item As New MenuItem()
With item
.Text = node.Attribute("text")
.NavigateUrl = node.Attribute("navigateurl")
.ToolTip = node.Attribute("tooltip")
.ImageUrl = "tool.png"
End With
parentItem.ChildItems.Add(item)
Next
With parentItem
.Text = element.Attribute("text")
.NavigateUrl = element.Attribute("navigateurl")
.ToolTip = element.Attribute("tooltip")
.ImageUrl = "heart.png"
End With
menu.Items.Add(parentItem)
Next
End Sub
Essentially, the code-behind will load an XML file called web.sitemap
(it can be any XML file you prefer) and iteratively add navigation nodes to the menu. This way, you can even add personalized nodes to the navigation as needed.
Points of Interest
Why using a Publishing Site?
Well, most of the SharePoint custom site definitions require you to use
a definition that's based from the publishing one. In order to get
user-swappable master pages and alternate CSS files, you have to start
with the publishing site.
Why not XmlSiteMapProvider
?
If you use that provider (out of the box from ASP.NET 2.0), you will not be able to modify the nodes in the navigation. The Items property of this object will have zero items for you to modify.
Why LINQ to XML?
Of course, using LINQ to XML is not needed. It merely simplifies hard-to-decipher nested loops or XPath that usually comes with XML document processing.
History
[May 14, 2008] Added a short FAQ section.