Microsoft

Blog Categories

Subscribe to RSS feed

Archives

Controlling Dynamic Navigation with Custom Channel Properties

The essential challenge of creating MCMS navigation controls is that they must be completely dynamic – that is, they must render a correct navigational structure based on a posting’s position in an arbitrary channel hierarchy. "Arbitrary" is the key word in the precending sentence — using the Site Manager, the administrator can create whatever channel structure she chooses and can change it at any time. So a basic assumption of navigation code is that the position of any item in the hierarchy is unknown, and that the structure of the hierarchy is unknown as well (although it is discoverable). Given these constraints, how do we write MCMS navigation code?

A basic principle of operation is to use recursive methods that access properties of the Channel object. For example, in implementing a bread crumb, given a current Channel, we can get the parent channel by using the Parent property. The Parent property of that channel gives us the next channel towards the root. We stop when the Parent property is Null. Rendering a tree-view navigation is similar, but we recurse in the other direction — towards the leaves. Given a base channel, we get the child postings and channels using the AllChildren property. The postings are rendered and the channels are recursively visited, using the AllChildren propery to get their children, and so forth.

The key to making this all work is the concept of context. The static property Current on the Microsoft.ContentManagement.Publishing.CmsHttpContext class returns the current posting or channel (whatever we happen to be requesting at that time). This is always our starting point in rendering navigation. Building a bread crumb is easy – get the current object from the CmsHttpContext, get the Parent property to get the parent channel, and continue doing so recursively. Rendering a tree-view navigation is a bit harder.

Rendering a tree-view navigation requires us to start at a base channel and work our way towards the leaves. An individual posting in a channel is likely to be within the structure we wish to render and is most likely not the base of our navigation. So we must start at the current posting and work our way backwards recursively using the Parent property until we reach a base channel. How do we define that base channel?

One way is clearly to let backwards recursive scanning hit the root channel, but this is no good because then we are always rendering the entire channel hierarchy. We need another way to know when to stop — for example, at the base of a policy manual branch of the hierarchy. The solution is to introduce a custom channel property responsible for marking the content base, and making our navigation code sensitive to that. Starting at an arbitrary posting, then, we scan backwards looking for the contentBase custom channel property (stopping at the root channel is no contentBase is defined), stop our backwards recursion at that point, and render down from there.

Custom channel properties can also be used to further enhance navigation. Let’s say you want each channel to have sort order and sort direction specifiable by an administrator and not baked into navigation code. The solution is to introduce two custom channel properties, sortOrder and sortDirection, and write your navigation code to use these properties to figure out which order to render that branch of the hierarchy. Here is an example of a routine that returns the contents of a channel in the order specified by sortOrder and sortDirection custom channel properties:

/// <summary>
/// Obtains a list of MCMS channel items in a specified channel. No recursion is supported.
/// </summary>
/// <param name="channel">The channel to obtain items from.</param>
/// <returns>A ChannelItemCollection containing the Channels and Postings contained in the specified channel.</returns>
public static ChannelItemCollection GetChannelItems( Channel channel )
{
ChannelAndPostingCollection channelItems = channel.AllChildren;

// Obtain the sort specifications from the custom channel properties
string sortOrderString = GetCustomProperty( channel, _SortOrderPropertyName, "Default", true );
string sortDirectionString = GetCustomProperty( channel, _SortDirectionPropertyName, "Ascending", true );

// Convert sort order string to enumerated form
ChannelSortOrder sortOrder = ChannelSortOrder.Default;
try
{
sortOrder = ( ChannelSortOrder ) System.Enum.Parse( typeof( ChannelSortOrder ), sortOrderString, true );
}
catch ( ArgumentException )
{
// Ignore
}

// Convert sort direction string to enumerated form
ChannelSortDirection sortDirection = ChannelSortDirection.Ascending;
try
{
sortDirection = ( ChannelSortDirection ) System.Enum.Parse( typeof( ChannelSortDirection ), sortDirectionString, true );
}
catch ( ArgumentException )
{
// Ignore
}

// Sort the items according to the sort specification evaluated from the channel properties
SortChannelItems( channelItems, sortOrder, sortDirection );

return channelItems;
}

Here is the code for getting the value of a specified custom channel property. The recursive property controls whether we are looking only at the specified channel or at any ancestor channels, too. This allows us to define a custom channel property on a channel and have that value control that channel and all subchannels as well. Introducing the same property on a subchannel allows the administrator to override a value set at an ancestor level:

public static string GetCustomProperty( Channel channel, string propertyName, string defaultValue, bool recursive )
{
// Start by setting the return value to the default.
string propertyString = defaultValue;

// Start at the specified channel.
Channel currentChannel = channel;

// Walk backwards looking for a custom channel property of the specified name.
while ( currentChannel != null )
{
// Check to see if we found the property
CustomProperty propertyValue = currentChannel.CustomProperties[ propertyName ];
if ( propertyValue != null )
{
// We found it – set the return value and stop walking
propertyString = propertyValue.Value.Trim( );
break;
}
else
{
// We are only supposed to look at the specified channel
if ( ! recursive )
{
break;
}
currentChannel = currentChannel.Parent;
}
}

return propertyString;
}

I used these concepts on a client project recently and the system worked great. Due to the completely arbitrary nature of custom channel properties and the ability to define their meaning in your MCMS code, the applications of this concept are potentially limitless.

Leave a Reply