text: 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 :
  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.
  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 :
  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.
  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 :
  CompassStyleHorizonatalHTML - Generates a traditional horizontal style trailVerticalHTML - Generates a vertical list trailDHTMLMenu - Uses a DHTML menu.
  CompassStyle
  If you choose the DHTMLMenu option you will have to manually add a reference to the javascript in your HEAD area, something like this :
  <script src="siteCompassDHTML.js" type="text/javascript"></script>
  CssClassWhen 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.
  CssClass
  CssClassLinkThis 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.
  CssClassLink
  CurrentPageIndicatorThis is an indicator character(s) that is displayed against the current page. In the demo project I use *.
  CurrentPageIndicator
  ItemSeperatorThis is the separator between compass items.
  ItemSeperator
  PostFixCodeThis is any html code or text you want to add immediately before the closing DIV tag. This is enclosed in anchor tags.
  PostFixCode
  PreFixCodeThis is any html code or text you want to add immediately after the opening DIV tag. This is enclosed in anchor tags.
  PreFixCode
  CaseSensitiveUrlComparisonWhen 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.
  CaseSensitiveUrlComparison
  DivNameThis 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.
  DivName
  LinkLastItemThis indicates if the last item in the compass is a href link or not.
  LinkLastItem
  MaxItemsThis 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.
  MaxItems
  ModeNormal - Renders the normal site compass.DisplayOnly - Doesn't add the current page to the compass, just displays what is currently in the session
  Mode
  OverrideTargetThis allows you to override the target attribute for all compass items with this one.
  OverrideTarget
  SuppressIconsAllows you to prevent any compass item icons from being displayed.
  SuppressIcons
  DisplayTitleThe text you want to show in the Site Compass in reference to this page.
  DisplayTitle
  IconAltThe alternate text attribute of the compass item icon.
  IconAlt
  IconPathA root-relative path to the compass item icon image.
  IconPath
  LinkTargetThe target of the compass item link.
  LinkTarget
  SessionKeyThis 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.
  SessionKey
  Both the source and demo archives come with a CSS file to drive the control. There are two components to this stylesheet:
  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:
  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
  <link title="errata" media="screen" href="StyleSheet1.css" type="text/css" rel="stylesheet">
  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()
  public void ResetCompass()
  This function will reset the compass and remove all items currently in the session.
  public void ResetCompass(ArrayList compassData)
  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)
  public void ResetCompass(CompassItem item)
  Resets the compass to only contain the given compass item.
  public CompassItem GetCompassItemAt(int index)
  public CompassItem GetCompassItemAt(int index)
  Retrieves the compass item at the given index in the ArrayList.
  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.
  Page.Request.RawUrl
  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:
  Page
  SiteCompassItem
  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:
  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);
  }
  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.
  TestMe()
  Equals(object obj)
  ArrayList.Contains(..)
  In the case of this control, I use the following overrides to allow me to perform object comparisons:
  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.
  ToString()
  .Equals(..)
  The static variable _caseSensitiveUrlComparison allows me to set the behaviour of the ArrayList.Equals(..) function before I call it.
  _caseSensitiveUrlComparison
  ArrayList.Equals(..)
  For example, this portion of code handles stripping out compass items that are not required.
  //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);
  }
  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.
  Browsable(false)
  For example:
  [Browsable(false)]
  public override FontInfo Font
  {
  get { return base.Font; }
  }
  For more information on this see:
  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.
  HtmlTextWriter
  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:
  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:
  GetDesignTimeHtml()
  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:
  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
  output
  Render()
  designer
  false
  true
  ArrayList
  .Equals()
  .Indent
  .WriteLine()
  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
  Allow me to bow before your DHTML Kung Fu
  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.
  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
  string
  private ArrayList GetRenderData(bool designer)
  compassData = (ArrayList)Page.Session[this.SessionKey];
  General News Suggestion Question Bug Answer Joke Rant Admin
  Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.