Click here to Skip to main content
15,867,686 members
Articles / Web Development / HTML
Article

Site Compass - An ASP .NET implementation of cookie crumb navigation

Rate me:
Please Sign up or sign in to vote.
4.77/5 (18 votes)
13 Nov 200411 min read 73.7K   1.5K   66   6
Uses ASP .NET sessions to track and display a users navigation through your site

Sample Image - sitecompass.gif

Introduction

Although Cookie Crumb navigation may not sound familiar to you, it is something that you see almost everywhere on the internet. Without it, thousands of users would get very lost in thousands of websites. In fact, Code Project itself uses something similar. Have a look at the top of any article and you will see something like this :

C#
All Topics, C#, .NET >> C# Control >> Beginners

The basic concept is quite simple:  As a user navigates through a site, a list of previously visited pages is displayed. This allows users to easily get back to pages they have previously visited within the site.

There are many ways of implementing this idea. It seems the Code Project version uses hardcoded values and assumes that the user followed a specific path to the project. This article uses ASP.NET session data and a Web Custom Control to track the actual path a user took to the page they are viewing, although it is also possible to define a specific track.

Where It All Began

This project initially started out as an afternoon of playing around with creating an ASP.NET-based web custom control following the MSDN article here :

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbwlkwalkthroughcreatingcustomwebcontrols.asp

Over a few afternoons, the project turned into something that would be useable in the real world, so I decided to release it as my first Code Project article.

The Demo Project

If you want to get up and running quickly and come back to the technical nitty gritty later, this is what you need to do :

  1. Download the demo project.
  2. Extract the contents of the zip file to your IIS web root.
  3. In IIS manager, set the SiteCompassTest directory to have an application name.
  4. Launch your browser to http://yourserver/SiteCompassTest/WebForm1.aspx.
  5. Click the links to see Site Compass do its thing.

Developing with Site Compass

Adding the control to Visual Studio

Adding the control to a web page is very simple. In VS.NET, you can just use Add/Remove Toolbox Items and select control's DLL file. The Control will appear in the toolbox, and you can add it to a page.

Control Properties

Appearance

Properties that control how the Site Compass is displayed.

CompassStyle
HorizonatalHTML - Generates a traditional horizontal style trail
VerticalHTML - Generates a vertical list trail
DHTMLMenu - Uses a DHTML menu.

If you choose the DHTMLMenu option you will have to manually add a reference to the javascript in your HEAD area, something like this :

C#
<script src="siteCompassDHTML.js" type="text/javascript"></script>

CssClass
When rendered, the Site Compass is contained within a DIV tag. This property indicates the name of the CSS class to use for this DIV. There are 5 sample CSS classes in the project. This does not apply to the DHTML menu compass style.

CssClassLink
This indicates the name of the CSS class to use for the links in the Site Compass. This does not apply to the DHTML menu compass style.

CurrentPageIndicator
This is an indicator character(s) that is displayed against the current page. In the demo project I use *.

ItemSeperator
This is the separator between compass items.

PostFixCode
This is any html code or text you want to add immediately before the closing DIV tag. This is enclosed in anchor tags.

PreFixCode
This is any html code or text you want to add immediately after the opening DIV tag. This is enclosed in anchor tags.

Behaviour

General Behaviour properties

CaseSensitiveUrlComparison
When a Site Compass is rendered, it checks the current page item values against the items already held in the session. If a match is found, then all items after it are removed. If you set the control to compare on case, then page1.htm and Page1.htm will be considered different pages.

DivName
This is the name attribute to assign to the DIV containing the Site Compass. This can be useful if you want to refer to it using Javascript.

LinkLastItem
This indicates if the last item in the compass is a href link or not.

MaxItems
This indicates the maximum number of items to display in the Site Compass. If the total number of items exceeds this value, the first items are hidden. If the user back tracks, the old items will reappear.

Mode
Normal - Renders the normal site compass.
DisplayOnly - Doesn't add the current page to the compass, just displays what is currently in the session

OverrideTarget
This allows you to override the target attribute for all compass items with this one.

SuppressIcons
Allows you to prevent any compass item icons from being displayed.

Compass Item Properties

This is the information that is stored in the session

DisplayTitle
The text you want to show in the Site Compass in reference to this page.

IconAlt
The alternate text attribute of the compass item icon.

IconPath
A root-relative path to the compass item icon image.

LinkTarget
The target of the compass item link.

Miscellaneous Properties

SessionKey
This is a unique key to identify the session data driving the SiteCompass. Using this property, you can have multiple compasses on a single page, all tracking different things--as long as each has a different SessionKey value.

The CSS

Both the source and demo archives come with a CSS file to drive the control. There are two components to this stylesheet:

  1. The DIV and HREF link styles
  2. The DHTML Menu Styles

You can play with these styles to your hearts content, but be careful when playing with the DHTML menu style as "interesting" things can and will happen. (Refer to the credit link at the bottom of the article for more information on this menu.) If you choose not to use the DHTML menu, then this portion of the style sheet can be removed.

One other thing to consider when generating styles is that the control contains a series of links that have been visited by the user. So this portion is next to useless:

HTML
A.YourLinkStyleName:link
{
    TEXT-DECORATION            : none;
    COLOR                    : aqua;
}

Just in case you need it, you can add stylesheet to your page by adding the following code to the head of your ASPX page

HTML
<link title="errata" media="screen" href="StyleSheet1.css" type="text/css" rel="stylesheet">

Public Functions

public void GenerateCompass_HTML(HtmlTextWriter output, bool designer)
public void GenerateCompass_DHTMLMenu(HtmlTextWriter output, bool designer)

The only reason these two functions are public is so that there is design time support for the control. Basically these are the two renderer functions that generate the HTML output for both design and run time.

public void ResetCompass()

This function will reset the compass and remove all items currently in the session.

public void ResetCompass(ArrayList compassData)

This function will reset the compass and add the given ArrayList of compass items as the data driving the compass.

public void ResetCompass(CompassItem item)

Resets the compass to only contain the given compass item.

public CompassItem GetCompassItemAt(int index)

Retrieves the compass item at the given index in the ArrayList.

A few things to note

When the control is rendered, the entire HTML code for it is placed within <DIV> tags. As you can see above, there are various properties that allow you to control this.

When a page is added to the control session data, it uses Page.Request.RawUrl to get the current page path, relative to the root of the server. This will also retrieve any querystring assigned to the URL. So page1.htm and page1.htm?id=2 are different pages as far as the control is concerned.

How It Works or The Nitty Gritty

As a user views a page with a Site Compass control on it, certain information is harvested and stored in the user's session. This data is sourced from two locations:

  1. The Page parameter.
  2. The properties you assign to the control.
This data is placed into an object of a class (SiteCompassItem) and then placed into an ArrayList so that the order in which it was received can be maintained. When the ASP.NET engine requests that the control generates its HTML, it checks the current page against the pages already held in the session. If the page exists, then all items after it are removed. If it doesn't exist, then it is added to the end of the current items. This list is then used to generate HTML code which renders the compass in the browser.

The Code

I am not introducing any amazing new ideas or concepts here, so the code is relatively simple to follow and I have endeavoured to ensure that it is fully commented at every stage. That being said, there are a few points of interest that are worthy of a mention here.

Comparing Objects

The heart of the control is its ability to check if a page already exists. The first incarnation of the control simply used a few concatenated strings to store its data, but this seemed a little stone age to me, so I looked for a better solution. In the end, I created my own custom class to hold compass item data, but this resulted in an interesting problem.

As an example, look at this code:

C#
private bool TestMe()
{
 testClass tc1 = new testClass("abc", "def", "ghi");
 testClass tc2 = new testClass("123", "456", "789");
 testClass tc3 = new testClass("xxx", "yyy", "zzz");
 testClass tc4 = new testClass("abc", "def", "ghi"); //same values tc1
 
 ArrayList array1 = new ArrayList();
 array1.Add(tc1);
 array1.Add(tc2);
 array1.Add(tc3);
 return array1.Contains(tc4);
}
Which uses this class
C#
class testClass
{
 public string abc;
 public string def;
 public string ghi;
 
 public testClass(string abc, string def, string ghi)
 {
  this.abc = abc;
  this.def = def;
  this.ghi = ghi;
 }
}

The TestMe() function will return false. To get around this problem you need to override the classes Equals(object obj) function as ArrayList.Contains(..) uses this to compare objects.

In the case of this control, I use the following overrides to allow me to perform object comparisons:

C#
public static bool _caseSensitiveUrlComparison = false;

...
...
...

public override bool Equals(object obj)
{
 CompassItem compareObject = obj as CompassItem;

 if (compareObject == null)
 {
  return false;
 }
            
 if (_caseSensitiveUrlComparison)
 {
  return this.ToString() == compareObject.ToString();
 }
 else
 {
  return this.ToString().ToLower() == compareObject.ToString().ToLower();
 }
}

public override int GetHashCode()
{
 return base.GetHashCode();
}

public override string ToString()
{
 return this.DisplayTitle + this.RawUrl + this.IconPath + this.IconAlt + this.Target;
}

The ToString() function simply takes all the class variables strings and concatenates them into one long string. When .Equals(..) is called by ArrayList.Contains(..), it uses this string to perform comparisons.

The static variable _caseSensitiveUrlComparison allows me to set the behaviour of the ArrayList.Equals(..) function before I call it.

For example, this portion of code handles stripping out compass items that are not required.

C#
//set the value of the static variable that
//indicates if object searches are case
//sensitive
CompassItem.CaseSensitiveUrlComparison = this.CaseSensitiveUrlComparison;

//check if the newItem exists in the current
//compass data this is possible as CompassItem
//has an overriden .equals()
if (compassData.Contains(newItem))
{
 int index = compassData.IndexOf(newItem) + 1;
 int count = compassData.Count - index;
 compassData.RemoveRange(index, count);
}

Overriding Default Properties

When you create a Custom Control using the methods describe in the MSDN article linked above, you will have various default properties. In the case of this project, there were various properties that I didn't want to display as they were not relevant, used, or important to the control

Hiding these properties is simply a question of setting the Browsable(false) attribute.

For example:

C#
[Browsable(false)]
public override FontInfo Font
{
 get { return base.Font;    }
}

For more information on this see:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemComponentModelBrowsableAttributeClassTopic.asp

If you look at the code, the properties I have hidden all relate to styles. The reason they are hidden is that I was entirely happy (HTML validation) with the quality of HTML code generated, when used in conjunction with HtmlTextWriter, so I decided to make the control entirely driven by style sheets.

Visual Studio Designer Support

There is nothing more annoying than developing with a control that has little or no designer support. This control has full designer support (except when you choose DHTMLMenu style) as it generates dummy entries to show the control as you would when you view it in a page.

How this is done is described in the MSDN article at the top of this page, but I thought I should cover it here as I use the run time control renderers to produce the design time control

I have created the following class that resides in the same namespace as the control class:

C#
public class SiteCompassDesigner : System.Web.UI.Design.ControlDesigner 
{
 public override string GetDesignTimeHtml() 
 {
  //create the writers to handled the output
  StringWriter sw = new StringWriter();
  HtmlTextWriter tw = new HtmlTextWriter(sw);

  //get and instance of the object calling the class
  SiteCompass scc = (SiteCompass) Component;

  //switch on the users selected style to generate
  //the necessary designer code.
  switch (scc.CompassStyle)
  {
   case SiteCompass.CompassStyles.HorizonatalHTML:
   case SiteCompass.CompassStyles.VerticalHTML:
    scc.GenerateCompass_HTML(tw, true);
    break;
   case SiteCompass.CompassStyles.DHTMLMenu:
    scc.GenerateCompass_DHTMLMenu(tw, true);
    break;
  }
            
  //return the StringWriter string to VS
  return sw.ToString();
 }
}

The overridden GetDesignTimeHtml() function is called by Visual Studio when you open an ASPX page in design view. Whatever string you return is placed where you place your control on the page. You could even do something really simple like:

C#
public class SiteCompassDesigner : System.Web.UI.Design.ControlDesigner 
{
 public override string GetDesignTimeHtml() 
 {
  return "<b>My Cool Control</b>"
 }
}

But that's not that cool is it!

In the case of the MSDN article, it generates a label and returns the HTML for it all within the GetDesignTimeHtml() function. I didn't want to have to code two variations of the compass renderers for runtime and design time support

Depending on the style of Site Compass you choose 1 of 2 renderers will be used:

  1. public void GenerateCompass_HTML(HtmlTextWriter output, bool designer)
  2. public void GenerateCompass_DHTMLMenu(HtmlTextWriter output, bool designer)

As you can see each takes the same parameters. When running in runtime mode the ASP.NET engine calls:

protected override void Render(HtmlTextWriter output)

The output parameter is the current pages HtmlTextWriter. The control then adds its own html into the writer and passes it back to the engine for further code additions and processing. My Render() function calls the renderers described above passing designer as false. This means that data is retrieved from the session and used to generate the compass. In design time there is no session so when designer is true the code produces a dummy list of items and produces HTML code for that which is passed back to Visual Studio via the GetDesignTimeHtml() function mentioned earlier

Possible Known Issues

  1. Querystrings will always be used to compare, a future version may have the ability to process querystrings

Possible Enhancements

  1. The ability set property driven list tags for the vertical list, at the moment the control simple adds a <BR> after each item.
  2. Override and expose more of the ArrayList functions to enhance developer control.
  3. The ability to track an item but not display it
  4. Enhance comparison options for the override .Equals()
  5. Improve the readability of the output code using .Indent and .WriteLine() on the HtmlTextWriter

DHTML Menu Credit

As a flight of fancy, I built in the ability to render the control as a DHTML menu. While this seemed like a good idea, my Kung Fu is weak when it comes to the dark arts of DHTML so I must give credit to the source of the tutorial that allowed me to produce the code for the Compass Menu style

http://www.brainjar.com/

Allow me to bow before your DHTML Kung Fu

History

v1.0.0.0 - This article this code

Thats all folks, thanks for tuning in.

One small thing: I unfortunately do not have anywhere to host a working sample of this project.  If anybody out there can spare a little bandwidth and server space, please contact me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSessionKey Pin
picksixweb25-Apr-07 8:59
picksixweb25-Apr-07 8:59 
GeneralRe: SessionKey Pin
MrEyes25-Apr-07 22:17
MrEyes25-Apr-07 22:17 
It has been a long time since I have looked at this code, however IIRC the session key is a value used the stored the trail data in the current session. The type for this is string therefore you could assign just about any value to it. The reason for a dynamic vs a static session key is that you can have multiple trails on a single page each with different settings etc etc as long as these have different session keys.

Have a look at the private ArrayList GetRenderData(bool designer), in this method you can see the session key being used to retrieve an array list of compass items:

compassData = (ArrayList)Page.Session[this.SessionKey];


I have to admit, I have never thrown this code at a the .NET 2 framework, so there may be something there that is breaking.

Hopefully that information will help, however if it doesnt post up and I will take a closer look
GeneralA Bit Of Web Space Pin
SAInstone5-Nov-05 3:17
SAInstone5-Nov-05 3:17 
GeneralVolunteer for hosting Pin
Member 16296835-Jan-05 5:21
Member 16296835-Jan-05 5:21 
GeneralRe: Volunteer for hosting Pin
MrEyes5-Jan-05 5:34
MrEyes5-Jan-05 5:34 
GeneralYikes, no comments Pin
MrEyes7-Dec-04 4:24
MrEyes7-Dec-04 4:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.