Stefan Goßner

Senior Escalation Engineer for SharePoint (WSS, SPS, MOSS, SP2010) and MCMS

Blogs

ASP.NET 2.0 and MCMS - The easy way to site navigation

  • Comments 19
  • Likes

[This article has been replaced with a new improved version.]

As outlined in the article I posted yesterday ASP.NET 2.0 provides new concepts to implement site navigation based on new server controls and the Site Map Provider concept. The controls and the provider work together and allow the implementation of a very flexible and scalable solution for site navigation.

The SiteMapProvider represents the data layer while the controls represent the presentation layer of the navigation. The SiteMapProvider provides information about the different elements in the navigation structure through SiteMapNode objects which need to be populated by the provider with the relevant information from the underlaying datasource. SiteMapNode allows to provide a Title, a URL and a Description. In addition during creation of the SiteMapNode a unique key for the Node has to be provided.

For MCMS the datasource is usually the channel structure. A SiteMapProvider for MCMS would have to read the information about the requested channel item from the MCMS repository and has to return a SiteMapNode object that contains the relevant information about this channel item:

     ChannelItem ci = ...;
     SiteMapNode smn = new SiteMapNode(this, ci.Guid); 
     smn.Url = ci.Url; 
     smn.Title = ci.DisplayName; 
     smn.Description = ci.Description;

The code above shows how to create a SiteMapNode based on a MCMS channel item. Here we are using the GUID of the channel item as unique key, the Url property for the Navigation URL. In addition we copy the Description and DisplayName properties to the Description and Title properties of the SiteMapNode object. Instead of the GUID we could also have used the Path property but as this property will later be used to retrieve the associated channel item from the repository it's better to use the GUID as a Searches.GetByGuid method call is quicker than a Searches.GetByPath method call.

A custom SiteMapProvider has to implement at least the following methods:

  • public override SiteMapNode FindSiteMapNode(string rawUrl)
    This method returns a SiteMapNode that is identified by a specific URL.
  • public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
    This method returns a collection of SiteMapNodes for all child objects of a given node.
  • public override SiteMapNode GetParentNode(SiteMapNode node)
    This method returns the parent node of a given node.
  • protected override SiteMapNode GetRootNodeCore()
    This method returns the root node of the Site Tree.

A SiteMapProvider can implement some more methods but only the methods above are really required for the controls shipped with ASP.NET 2.0.

A very basic SiteMapProvider for MCMS which can be used for MCMS navigation controls would look like the following:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Microsoft.ContentManagement.Publishing;

namespace StefanG.SiteMapProviders
{
    public class MCMSSiteMapProvider : SiteMapProvider
    { 
 
        // Generate SiteMapNode object from a MCMS ChannelItem
        // We are using the Display Name as the Title and the GUID of the 
        // channel item as the unique key for the SiteMapNode object
        // This allows easy lookup of the channel item in the MCMS repository.
        protected SiteMapNode GetSiteMapNodeFromChannelItem(ChannelItem ci)
        {
            SiteMapNode smn = null;
            if (ci != null)
            {
                smn = new SiteMapNode(this, ci.Guid);
                smn.Url = ci.Url;
                smn.Title = ci.DisplayName;
                smn.Description = ci.Description;
            }
            return smn;
        } 
        // Retrieve the MCMS Channel item identified by the given URL and
        // return a SiteMapNode for it
        public override SiteMapNode FindSiteMapNode(string rawUrl)
        {
            ChannelItem ci = EnhancedGetByUrl(CmsHttpContext.Current, rawUrl);
            return GetSiteMapNodeFromChannelItem(ci);
        }

        // Generate SiteMapNodes for all child channels and child postings
        // of the channel identified by the given node and return them as 
        // SiteMapNodeCollection
        public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
        {
            SiteMapNodeCollection smnc = new SiteMapNodeCollection();

            Channel channel = CmsHttpContext.Current.Searches.GetByGuid(node.Key) as Channel;
            if (channel != null)
            {
                ChannelCollection cc = channel.Channels;
                cc.SortByDisplayName();
                foreach (Channel c in cc)
                {
                    smnc.Add(GetSiteMapNodeFromChannelItem(c));
                }

                PostingCollection pc = channel.Postings;
                pc.SortByDisplayName();
                foreach (Posting p in pc)
                {
                    smnc.Add(GetSiteMapNodeFromChannelItem(p));
                }
            }
            return smnc;
        } 

        // Retrieve the parent node of the ChannelItem identified by the given node
        // and generate a SiteMapNode object for it.
        public override SiteMapNode GetParentNode(SiteMapNode node)
        {
            ChannelItem ci = CmsHttpContext.Current.Searches.GetByGuid(node.Key) as ChannelItem;
            return GetSiteMapNodeFromChannelItem(ci.Parent);
        } 

        // Retrieve the root channel and generate a SiteMapNode object for it.
        protected override SiteMapNode GetRootNodeCore()
        {
            Channel root = CmsHttpContext.Current.RootChannel;
            return GetSiteMapNodeFromChannelItem(root);
        } 
 
        // Helper function to check if the "Map Channel name to Host Header name"
        // feature is enabled or not
        private bool MapChannelToHostHeaderEnabled(CmsContext ctx)
        {
            return (ctx.RootChannel.UrlModePublished == "http://Channels/");
        } 
 
        // Replacement for the Searches.GetByUrl method as the original one
        // does not work correct with host header mapping enabled.
        // details: http://support.microsoft.com/?id=887530
        private ChannelItem EnhancedGetByUrl(CmsContext ctx, string Url)
        {
            if (MapChannelToHostHeaderEnabled(ctx))
            {
                string Path = HttpUtility.UrlDecode(Url);
                Path = Path.Replace("http://""/Channels/");
                if (!Path.StartsWith("/Channels/"))
                    Path = "/Channels/" + 
                           HttpContext.Current.Request.Url.Host + Path;
                if (Path.EndsWith(".htm"))
                    Path = Path.Substring(0, Path.Length - 4);
                if (Path.EndsWith("/"))
                    Path = Path.Substring(0, Path.Length - 1);
                return (ChannelItem)(ctx.Searches.GetByPath(Path));
            }
            else
                return ctx.Searches.GetByUrl(Url);
        }
    }
}

To use the above SiteMapProvider you need to add the code above to a C# class library project and compile it into a DLL. Then add the provider to your ASP.NET 2.0 template project web.config file as follows:

    <system.web>
        <siteMap defaultProvider="MCMSSiteMapProviderenabled="true">
            <providers>
                <add name="MCMSSiteMapProvider" type="StefanG.SiteMapProviders.MCMSSiteMapProvider, MCMSSiteMapProvider"/>
            </providers>
        </siteMap>
    </system.web>

That's all! Now the ASP.NET 2.0 navigation controls can use this SiteMapProvider. No further coding is required! It is possible to add multiple different SiteMapProviders to your site. This makes sense if your have different kind of controls where some items should be shown or hidden based on your business needs. E.g. one provider should only return channels with a specific custom property. To achieve this implement a second provider that checks for these properties in the GetChildNode method and bind this SiteMapProvider explicitly to your control.

ASP.NET 2.0 ships with three new controls that can be used for site navigation:

  • SiteMapPath
  • Menu
  • TreeView

The SiteMapPath control - which behaves like the Woodgrove Breadcrumb control - requires a SiteMapProvider and cannot be used without it. Just drag a SiteMapPath control on your template or channel rendering script and your MCMS bread crumb is ready - if you configured the SiteMapProvider above in your web.config.

The Menu control is a nice multi level fly out control implemented using client side javascript. This is similar to the top navigation in Woodgrove. The Tree View control is similar to the left navigation control in Woodgrove.

To use the Menu and the TreeView control with our SiteMapProvider you first need to drag a SiteMapDataSource object to your template or channel rendering script. Either explicitly configure the SiteMapProvider to be used using the SiteMapProvider property or let this property blank and the configured default provider will be used. Then drop the TreeView or Menu control to your template or channel rendering script and configure the SiteMapDataSource you dropped earlier as the datasource to use for the control.

One hint for the TreeView control: you should ensure that child nodes are populated on demand - otherwise all nodes are retrieved when the page is first requested which can slow down your MCMS site significantly if your provider enumerates the whole repository. To do this you need to set the PopulateOnDemand property in the TreeNode Databinding properties to true.

Comments
  • When switching from published mode to edit mode using a template that holds the ASP.NET 2.0 navigation...

  • ASP.NET 2.0 ships with many new composite controls to make developing a web site very easy. I have covered...

  • ASP.NET 2.0 ships with many new composite controls to make developing a web site very easy. I have covered...

  • ASP.NET 2.0 ships with many new composite controls to make developing a web site very easy. I have covered...

  • ASP.NET 2.0 ships with many new composite controls to make developing a web site very easy. I have covered...

  • In my previous article I have discussed how to implement site navigation for an MCMS site using ASP.NET...

  • In my previous article I have discussed how to implement site navigation for an MCMS site using ASP.NET...

  • In my previous article I have discussed how to implement site navigation for an MCMS site using ASP.NET...

  • ASP.NET 2.0 ships with many new composite controls to make developing a web site very easy. I have covered...

  • I have now implemented this, as my first piece of work using CMS and Visual Studio 2005.

    I had some issues with the syntax of the web.config sitemapprovider statement. I then found that the code above only worked for one of the navigation controls. I replaced it with the code from the sample on GotDotNet. That fixed some problems, but the tree control still didn't work (only showed the root).

    Debugging, I found that the FindSiteMapNode(string rawUrl) function was actually being passed the entry GUID not the URL. I changed the code to the following, and now all the controls work:

    public override SiteMapNode FindSiteMapNode(string rawUrl)
    {
    // rawUrl may be a GUID
    ChannelItem ci = CmsHttpContext.Current.Searches.GetByGuid(rawUrl) as ChannelItem;
    if (ci == null)
    ci = EnhancedGetByUrl(CmsHttpContext.Current, rawUrl);
    return GetSiteMapNodeFromChannelItem(ci);
    }

  • If you try to add a new page in a channel using a template that holds the ASP.NET 2.0 navigation controls you will get tha follow error:
    "Exception Details: System.NullReferenceException: Object reference not set to an instance of an object
    [NullReferenceException: Object reference not set to an instance of an object.]
    CMSNavigation.MCMSSiteMapProvider.GetParentNode(SiteMapNode node) in C:\Documents and Settings\geonik\My Documents\Visual Studio 2005\Projects\CMSNavigation\CMSNavigation\Class1.cs:96
    System.Web.SiteMapNode.get_ParentNode() +23
    System.Web.UI.WebControls.SiteMapPath.CreateControlHierarchy() +66......"

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
Raw Html Fix