|

Introduction
Sitemaps and breadcrumbs (SiteMapPath) are fantastic. For a dynamic site, they can be crucial, since search engines are historically agnostic to QueryString driven content that is common for dynamic websites. A sitemap can help search engines find this content and index it appropriately, expose breadcrumbs for users, and help users see in one place everything you have to offer them.
The out-of-the-box functionality for Sitemaps with ASP.NET 2.0 is fantastic. Many of the complexities previously associated with them have been addressed; however, while implementing one for our system we faced a number of challenges which were not addressed by Microsoft.
My code and approach in this article will help you if you share any of the following motivations with me:
- You are using a hierarchical DataSet to generate the tree for your dynamic content which would feed a sitemap
- You want to generate the XML needed for the ASP.NET SiteMap datasource straight from the DataSet in a fast way that minimizes lines of code
- Your content management system is large enough to vote against simply generating this DataSet on every web request (remember it feeds breadcrumbs on all your pages) or filling up your server cache with this huge object that represents all the content in your system - thus you want a static XML SiteMap file that is updated every day (or at a frequency that you define)
- You want to backup your XML SiteMap files for maintenance or just safety
- You have multiple content sections in your dynamic CMS that are different trees - meaning your pages are context sensitive and would feed off of separate sitemaps in the same web application
- You want to learn how to do an XSLT in memory in .NET 2.0
- You want to learn how to use embedded resource files in .NET 2.0
- You just want to see the XSLT that can turn a hierarchical DataSet into a *.sitemap file
- You want to see how to handle identical Urls in the sitemap (which is forbidden by the out-of-the-box SiteMapProvider)
Step 1: Identify your naming convention
To begin, you must define your naming convention for your XML file. As this is an automated process you want to shield the client from thinking of its inner workings. In my case, I needed 3 components to uniquely identify my sitemaps:
ApplicationID (the ID for the web application of interest) CultureName (the culture name for the current content i.e. en-US or en-GB) SitemapType (the content tree that uniquely identifies groups of content for separation)
I have the following Fields in my Sitemap class: #region Private Field Declarations private Guid applicationId; private string cultureName; private DataSet dataSource; private byte maxTiers; private SitemapType type; private string Id; private string fileName; private object[,] heirarchicalRelations = new object[,] { { new string[] { "ID" }, new string[] { "ParentID" } }, { new string[] { "ID" }, new string[] { "ParentID" } }, { new string[] { "ID" }, new string[] { "ParentID" } }, { new string[] { "ID" }, new string[] { "ParentID" } } }; #endregion
The enumeration: /// This distinguishing property allows you to define multiple content areas within one /// web application. Then you can call out the appropriate SiteMapProvider for different /// path contexts in a single web application. /// public enum SitemapType { /// /// A sitemap that lists all of the informational content in the system /// Content = 0, /// /// A sitemap that lists all of the properties and areas in the system /// Property = 1, }
The constructor builds the ID and the file name (I don't need the application ID in the filename because the files are stored within directories in their own application anyway). We use camel-cased naming conventions for private fields and Pascal cased naming conventions for public properties in our software. I have omitted the public Properties, but you can see that below they correspond to the private fields. ToString("G") formats to an enum's name.
/// Generates a Sitemap from its Type, application ID, and requested culture /// /// The unique ID for the application that the sitemap belongs to /// Defines the culture that the client would like to view the sitemap in. The application must support that culture. The standard abbreviation string must be used. /// The sitemap type that should be generated public CMSSitemap(Guid appId, string cName, SitemapType sType) { this.ApplicationID = appId; this.CultureName = cName; this.Type = sType; StringBuilder pathBuilder = new StringBuilder(this.Type.ToString("G")); pathBuilder.Append("_"); pathBuilder.Append(this.CultureName); pathBuilder.Append(".sitemap"); this.FileName = pathBuilder.ToString(); StringBuilder IdBuilder = new StringBuilder(this.ApplicationID.ToString()); IdBuilder.Append("_"); IdBuilder.Append(this.Type.ToString("G")); IdBuilder.Append("_"); IdBuilder.Append(this.CultureName); this.ID = IdBuilder.ToString(); BSData helper = new BSData(); SqlParameter p_applicationId = new SqlParameter("@ApplicationID", SqlDbType.UniqueIdentifier, 16, ParameterDirection.Input, false, ((Byte)(18)), ((Byte)(0)), "", DataRowVersion.Current, appId); SqlParameter p_cultureName = new SqlParameter("@CultureName", SqlDbType.VarChar, 10, ParameterDirection.Input, false, ((Byte)(18)), ((Byte)(0)), "", DataRowVersion.Current, cName); SqlParameter p_sitemapType = new SqlParameter("@SitemapType", SqlDbType.TinyInt, 3, ParameterDirection.Input, false, ((Byte)(18)), ((Byte)(0)), "", DataRowVersion.Current, sType); SqlParameter[] param = new SqlParameter[3] { p_applicationId, p_cultureName, p_sitemapType }; this.DataSource = helper.ReadOnlyHeirarchicalQuery(helper.BWEnterpriseReader, "dbo.[proc_getSitemapData]", this.heirarchicalRelations, param); this.modifyDuplicateUrls(); this.MaxTiers = (byte)this.DataSource.Tables.Count; }
Step 2: Take Care of Your Hierarchical Data
About our data layer (most likely you have your own methods, so populating your dataset is up to you). We use Stored Procedures exclusively to form the foundation of our Data Layer. They are called through ADO.NET helper methods where connections to the DB are isolated and encapsulated. They filter DataSets back to the business layer after building them. The method above, "ReadOnlyHeirarchicalQuery" will build a Hierarchical DataSet with DataRelations.
This private field heiarchicalRelations is questionable right now. In order to allow the client to call that method and define multiple parent-child columns, I used an object[,] (you can have multiple-column PKs & FKs). This has the drawback of requiring the caller to know how many tiers will come back to form the DataRelations. If I were to restrict the DataSet to have Parent and Children ID colums named the same thing exclusively, I could have a more confined but elegant solution, because I could generate the DataRelations on the fly based on how many DataTables came back from SQL. In this new scenario, the caller does not need to know how many DataRelations must be built. I will do this before release because our tiers are fluctuating now.
I know that you can manage your own hierarchical DataSets with your own methods, but I have provided this information to you as a proof of concept for my work.
Useful fact from a Microsoft conference - I did not give you the real name of my sproc up there, but notice that I prefix it with proc_. You should not prefix your sprocs with sp_ because when you do so, you slow down your entire data layer because SQL will search all of the system stored procedures before finding your sproc.
The method I call in the constructor is shown below for your reference: /// Allows for execution of multiple-select statements with one sproc, and heirarchical datasets with auto-creation of relations /// /// Connection string to the DB /// Name of sproc to execute /// Each object must be a string[], with column 0 of the multi-dimensional object[] being the string[] of Parent Column Names, and column 1 being the string[] of Child Column Names. /// This construct is required to support multi-column PK and FKs /// Parameter array for the stored procedure /// Heirarchical DataSet internal DataSet ReadOnlyHeirarchicalQuery(string comPathString, string sprocName, object[,] dataRelations, SqlParameter[] paramList) { SqlConnection comPath = new SqlConnection(comPathString); SqlCommand executeSproc = new SqlCommand(sprocName,comPath); executeSproc.CommandType = CommandType.StoredProcedure; foreach (SqlParameter p in paramList) { executeSproc.Parameters.Add(p); } DataSet finalResultSet = new DataSet("finalResultsDS"); try { comPath.Open(); SqlDataReader readSprocResults = executeSproc.ExecuteReader(); do { finalResultSet.Tables.Add(); foreach(DataRow r in readSprocResults.GetSchemaTable().Rows) { finalResultSet.Tables[finalResultSet.Tables.Count-1].Columns.Add(r[0].ToString(),Type.GetType(r[12].ToString())); } while (readSprocResults.Read()) { addRow = finalResultSet.Tables[finalResultSet.Tables.Count-1].NewRow(); foreach (DataColumn c in finalResultSet.Tables[finalResultSet.Tables.Count-1].Columns) { addRow[c.Ordinal] = readSprocResults[c.Ordinal]; } finalResultSet.Tables[finalResultSet.Tables.Count-1].Rows.Add(addRow); } } while (readSprocResults.NextResult()); comPath.Close(); for (int i=0;i { string[] string[] DataColumn[] DataColumn[] for { pc[j] } for { cc[j] } DataRelation tempDR.Nested finalResultSet.Relations.Add(tempDR); } } catch { comPath.Close(); throw } return }
The only thing to note here if you are handling your own DataSet - the name of the DataSet is finalResultsDS. Remember that for the XSLT.
Step 3: Handle Duplicate Urls
One last step to initialize the data of your class - this.modifyDuplicateUrls();
I have a problem in my site hierarchy - the business users want to be able to categorize content in multiple places at times. This feeds the menu system as well. You can handle this another way - when you generate the DataSet - only choose one path for the content. In my case, this was not an option. The users want to see the content show up in the hierarchy that they defined.
Why does this pose as a problem? Think about what the SiteMap classes in the .NET framework must do to generate breadcrumbs - the HttpRequest defines a Url to hit, and now your SiteMapProvider has to figure out, well, "Where in the hierarchy does this Url correspond to." If there is more than one option, how does it know which path you meant? And then how does it generate breadcrumbs when your paths are not mutually exclusive? Exactly. It can't. You would need to write your own SiteMapProvider which has logic that makes decisions in that instance. I don't have time for that. I just wanted to keep the SiteMap Data Hierarchy's integrity, as defined by the business users, but still use the built in Provider. I did not want to forgo the optimizations and fine code given to me by Microsoft's built in provider. I simply did not see the need to invest that much time.
I achieved my goal by tricking it with a QueryString parameter. I keep the original Url the first time it is seen in the hierarchy. For duplicates I add a dummy QueryString Parameter that can be used to distinguish them. I do the same for my menuing system hierarchy so that the breadcrumbs match the menus. Yet still, if a user externally linked one of our pages, my CMS can resolve the content they want to get without a problem by providing a fallback. That is out of the scope of this article and begins to get into my custom CMS which is very powerful, multi-lingual system. All you need to know is that this method will allow you to trick the default SiteMapProvider into behaving how I needed it to for my requirements. The following method does the trick: /// SiteMap datasources cannot have duplicate Urls with the default provider. /// This finds duplicate urls in your heirarchy and tricks the provider into treating /// them correctly /// private void modifyDuplicateUrls() { StringCollection urls = new StringCollection(); string rowUrl = String.Empty; uint duplicateCounter = 0; string urlModifier = String.Empty; foreach (DataTable dt in this.DataSource.Tables) { foreach (DataRow dr in dt.Rows) { rowUrl = (string)dr["Url"]; if (urls.Contains(rowUrl)) { duplicateCounter++; if (rowUrl.Contains("?")) { urlModifier = "&instance=" + duplicateCounter.ToString(); } else { urlModifier = "?instance=" + duplicateCounter.ToString(); } dr["Url"] = rowUrl + urlModifier; } else { urls.Add(rowUrl); } } } } }
Step 4: Write your XSLT and Embed it in your Class Library
You need an XSLT to get your *.sitemap files into the right format straight from your DataSet. I have shown you below example DataSet.GetXML() output, the XSLT to transform it, and the resulting *.sitemap file. I have only included truncated snippets of the input and output for brevity. If you have a different naming convention for your DataSets and DataTables you will have to modify the XSLT accordingly. These files are included in the source code download.
Input XML generated from DataSet.GetXML()
<FINALRESULTSDS>
<TABLE1>
<ID>3efae161-e807-4419-98d5-162f69cca7da</ID>
<DESCRIPTION>Find and reserve corporate housing and serviced apartments anywhere in the world. - Find and Reserve</DESCRIPTION>
<URL>~/Apps/AdvancedPropertySearch.aspx?CM=2441358F-1E9D-4694-8E82-9E4FB4E6AD16</URL>
<ITEMORDER>0</ITEMORDER>
<TABLE2>
<ID>8115e4ef-153b-4b11-85bd-5e0a10d16c59</ID>
<PARENTID>3efae161-e807-4419-98d5-162f69cca7da</PARENTID>
<DESCRIPTION>Reservation Request</DESCRIPTION>
<URL>~/Apps/ReservationRequest.aspx</URL>
<ITEMORDER>0</ITEMORDER>
</TABLE2>
</TABLE1>
<TABLE1>
<ID>4d60e90d-8ede-4e52-88c9-65b9416ff02a</ID>
<DESCRIPTION>Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Accommodations Solutions</DESCRIPTION>
<URL>~/Apps/CMSTemplate.aspx?CM=4B042082-E8CB-4A34-A589-936F3642F7A1</URL>
<ITEMORDER>1</ITEMORDER>
<TABLE2>
<ID>6e49589e-3743-4ded-a718-41a615a5c4f0</ID>
<PARENTID>4d60e90d-8ede-4e52-88c9-65b9416ff02a</PARENTID>
<DESCRIPTION>Temporary housing for extended stays and business travel by BridgeStreet Worldwide corporate housing and serviced apartments. - Business Travelers</DESCRIPTION>
<URL>~/Apps/CMSTemplate.aspx?CM=64DA181A-7A9E-4811-AC0D-7A0C89827F28</URL>
<ITEMORDER>0</ITEMORDER>
</TABLE2>
<TABLE2>
<ID>ce5614cb-c8b4-449c-a494-8c91b3bb3328</ID>
<PARENTID>4d60e90d-8ede-4e52-88c9-65b9416ff02a</PARENTID>
<DESCRIPTION>Extended stay accommodations for military personnel and government travelers through BridgeStreet Worldwide corporate housing and serviced apartments. - Government Travelers</DESCRIPTION>
<URL>~/Apps/CMSTemplate.aspx?CM=6EF08802-4023-4F81-8C1E-1C3D9B4CEA86</URL>
<ITEMORDER>1</ITEMORDER>
</TABLE2>
XSLT. You can see that it supports 6 levels deep for your hierarchy. You can add more if you need to. Or I'm sure there's a better way to do this with recursion, but my strength is not XSLT so if someone out there can show a more elegant way to perform this, which does not require a limited level of nesting, please chime in on the comments. In the meantime, this will do the job, and it does it well:
="1.0" ="UTF-8" <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <xsl:apply-templates /> </xsl:template> <xsl:template match="/finalResultsDS"> <xsl:element name="siteMap" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> --> <xsl:for-each select="./*[starts-with(local-name(), 'Table')]"> <xsl:element name="siteMapNode" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <xsl:call-template name="transformElementsToAttributes" /> --> <xsl:for-each select="./*[starts-with(local-name(), 'Table')]"> <xsl:element name="siteMapNode" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <xsl:call-template name="transformElementsToAttributes" /> --> <xsl:for-each select="./*[starts-with(local-name(), 'Table')]"> <xsl:element name="siteMapNode" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <xsl:call-template name="transformElementsToAttributes" /> --> <xsl:for-each select="./*[starts-with(local-name(), 'Table')]"> <xsl:element name="siteMapNode" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <xsl:call-template name="transformElementsToAttributes" /> --> <xsl:for-each select="./*[starts-with(local-name(), 'Table')]"> <xsl:element name="siteMapNode" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <xsl:call-template name="transformElementsToAttributes" /> --> <xsl:for-each select="./*[starts-with(local-name(), 'Table')]"> <xsl:element name="siteMapNode" namespace="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <xsl:call-template name="transformElementsToAttributes" /> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:element> </xsl:template> <xsl:template name="transformElementsToAttributes"> <xsl:attribute name="url"> <xsl:value-of select="Url"/> </xsl:attribute> <xsl:attribute name="title"> <xsl:value-of select="Title"/> </xsl:attribute> <xsl:attribute name="description"> <xsl:value-of select="Description"/> </xsl:attribute> </xsl:template> </xsl:stylesheet>
Oh La la, I have shown you below the French Sitemap that is output from my fun class. This is the end product: ="1.0" ="utf-8" <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <siteMapNode url="/Default.aspx" title="Accueil" description="Accueil"> <siteMapNode url="~/Apps/AdvancedPropertySearch.aspx?CM=00CB5839-329F-4692-9A60-2D4A389DD6DB" title="Rechercher et R�server" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Rechercher et R�server"> <siteMapNode url="~/Apps/ReservationRequest.aspx" title="Demande de R�servation" description="Demande de R�servation" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=1EBB6235-4174-437C-B23F-31B9E58E4FC0" title="Solutions de Logement" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Solutions de Logement"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=09258BB3-66A3-4600-8ADF-4237E70CC0EA" title="R�sidences H�teli�res" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - R�sidences H�teli�res" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=B56EE30D-9168-4CE7-8968-AA395175DAD9" title="Locations" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Locations" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=A7C67AFF-89DB-4D1E-BDD4-E42E50A71AE5" title="Services D�assistance au D�m�nagement /Relocation" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Services D'assistance au D�m�nagement/Relocation" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=62DA6B92-D1E2-4AC3-A460-67C14086871C" title="Voyages de Loisirs" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Voyages de Loisirs" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=40ECF480-0ECA-4121-AB28-E1CC886E419E" title="Achat / Vente" description="Propositions d'appartements � vendre sur Paris - Achat / Vente" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=3093CA21-5CB4-4EDF-9C46-F0E6C8ED922F" title="Pourquoi des R�sidences H�teli�res" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Pourquoi des R�sidences H�teli�res ?"> <siteMapNode url="~/Apps/FAQ.aspx" title="FAQ" description="FAQ" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=0F626C88-6ABC-4B7F-8F71-5055184F7030" title="Am�nagements & Services" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Am�nagements & Services" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=22D5A46E-9EEF-44F9-9B61-AD80E776186C" title="� Quoi Vous Attendre" description="Grand choix d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - � Quoi Vous Attendre" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=B1F35382-ED7C-400F-970C-B620D14590C5" title="T�moignages" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - T�moignages" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=8A2B0A11-74AD-4551-96EF-C13428E3E227" title="R�le Dans le Secteur" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - R�le Dans le Secteur" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=25D55721-C606-4C42-8F98-7EDBABABA819" title="Qui Sommes-nous" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Qui Sommes-nous"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=9D112AFA-7759-49FD-851D-51F1008ACF9F" title="Distinction Honorifique" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Distinction Honorifique" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=E3DB47D4-B255-4D98-BE18-75710EEDC265" title="Notre Culture" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Notre Culture"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=62C3EE6C-535F-48B3-98D3-36C7005E563D" title="Valeurs/ Mission" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Valeurs &amp; Mission" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=9F1FF39F-B1C2-4306-855E-EAE221FC74A9" title="Notre Promesse" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Notre Garantie de Satisfaction" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=6FCD4B03-718E-4D99-BE42-8623C86F639B" title="BridgeCare" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - BridgeCare" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=4401C4EC-0FF8-4FE5-9924-2251BA0DBFD6" title="Nos Collaborateurs" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Nos Collaborateurs" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=3237E7F6-F732-4F24-9E65-C761B0AE8800" title="Carri�res" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Carri�res" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=885399BD-44A4-4720-8C75-F6521ED3A2D4" title="Partenariat Mondial" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Partenariat Mondial"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=2E1B9DAA-A2AC-48CD-8716-2EF113E71973" title="BridgeNet Login" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Bienvenue � Nos Partenaires Mondiaux" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=1B27D0CC-53C5-4A78-A5E0-BD4AA8A51C94" title="BridgeStreet Global Alliance" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - BridgeStreet Global Alliance" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=ADAF636D-0481-4ECC-B918-4BBF1D11FD4E" title="Des Solutions Faciles Pour L�h�bergement D�affaires" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Des Solutions Faciles Pour L�h�bergement D�affaires"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=5295303A-1EEA-4FF5-A913-2842540F0762" title="Programmes Tech" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Programmes Tech" /> </siteMapNode> <siteMapNode url="~/Apps/Announcements.aspx?CM=50C83AAB-76F1-4BB9-88BA-720A116286BA" title="Actualit� de L�entreprise" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Corporate News - September 7, 2006"> <siteMapNode url="~/Apps/ClientNewsletter.aspx?CM=814DD7F3-CE82-45E7-8CB0-1955E157F7A6" title="Newsletter de L�entreprise" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Client Newsletter - Q3 2006"> <siteMapNode url="~/Apps/ClientNewsletter.aspx?CM=295E14E8-2B3C-A4E6-6534-19508E03700E" title="Q2 2006 Client Newsletter" description="Luxury serviced accommodations for both short term and extended stay letting periods throughout London and its surrounding areas in England, by BridgeStreet Serviced Apartments UK. - Client Newsletter - Q2 2006" /> <siteMapNode url="~/Apps/ClientNewsletter.aspx?CM=814DD7F3-CE82-45E7-8CB0-1955E157F7A6&instance=5" title="Q3 2006 Client Newsletter" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Client Newsletter - Q3 2006" /> <siteMapNode url="~/Apps/ClientNewsletter.aspx?CM=89B1D5B2-CECA-445E-99E7-31827CCA5D68" title="Fall/Winter 2005 Client Newsletter" description="Fall/Winter 2005 Client Newsletter - Client Newsletter - Fall/Winter 2005" /> <siteMapNode url="~/Apps/ClientNewsletter.aspx?CM=40753C0C-C80A-4FA9-B926-DC077C30EAD9" title="Q1 2006 Client Newsletter" description="Q1 2006 Client Newsletter - Client Newsletter - Q1 2006" /> </siteMapNode> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B11F-2B3C-A4E6-6126-05FAD803B16D" title="BridgeStreet Worldwide's Global Partners Expand Company's West Coast Presence" description="BridgeStreet Worldwide's Global Partners Expand Company's West Coast Presence - Company Enters Pacific Northwest and San Francisco Markets - Corporate News - November 16, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B17D-2B3C-A4E6-6098-06F29E5EF2F3" title="BridgeStreet Worldwide Receives 2005 Technology ROI Award" description="BridgeStreet Worldwide Receives 2005 Technology ROI Award - Winners Use Technology Solutions to Achieve Positive Business and Financial Results - Corporate News - August 17, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B0E1-2B3C-A4E6-6183-0A4E10FFF887" title="BridgeStreet Worldwide Announces Corporate Housing Partnership With AMLI Residential" description="BridgeStreet Worldwide Announces Corporate Housing Partnership With AMLI Residential - - Corporate News - January 23, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B352-2B3C-A4E6-6EC6-0C181C2644D3" title="BridgeStreet's Licensed Global Partner Program Signs First Three Partners" description="BridgeStreet's Licensed Global Partner Program Signs First Three Partners - - Corporate News - March 17, 2003" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B19C-2B3C-A4E6-6C44-1198E3258BAA" title="BridgeStreet Worldwide and Global Partner Furnished Quarters Expand Florida Presence" description="BridgeStreet Worldwide and Global Partner Furnished Quarters Expand Florida Presence - Four New Locations Enlarge Florida Corporate Housing Inventory - Corporate News - July 20, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B333-2B3C-A4E6-61C3-174EAD2204B5" title="BridgeStreet Corporate Housing Acquires Circus Apartments in London's Canary Wharf" description="BridgeStreet Corporate Housing Acquires Circus Apartments in London's Canary Wharf - - Corporate News - March 25, 2003" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=485AE1BD-2B3C-A4E6-6C50-1BE7D65A9A0E" title="BridgeStreet Worldwide Launches Luxury SleepEasy<sup>sm</sup> Bedding Package" description="BridgeStreet Worldwide Launches Luxury SleepEasy<sup>sm</sup> Bedding Package - Homelike, High-Comfort Bedding Offered in Corporate Apartments Nationwide - Corporate News - April 19, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B1BC-2B3C-A4E6-6359-20EDB45507C7" title="BridgeStreet Worldwide Unveils Totally Revamped Web Site" description="BridgeStreet Worldwide Unveils Totally Revamped Web Site - Enhanced BridgeStreet.com Another Step in - Corporate News - June 21, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=485AE0F2-2B3C-A4E6-6836-2A26ECBEC7D6" title="BridgeStreet Worldwide Expands Locations Throughout Europe" description="BridgeStreet Worldwide Expands Locations Throughout Europe - Corporate Housing Leader Launches Global Alliance Program in Europe - Corporate News - May 18, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B313-2B3C-A4E6-69ED-303D12EF12E6" title="BridgeStreet Corporate Housing Names Lee Curtis President" description="BridgeStreet Corporate Housing Names Lee Curtis President - Thomas Vincent to Retire - Corporate News - August 21, 2003" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B2F4-2B3C-A4E6-6CE2-53F9F0068FD8" title="BridgeStreet Chicago Office Receives Second Consecutive �CAMME Award� From Chicagoland Apartment Association" description="BridgeStreet Chicago Office Receives Second Consecutive �CAMME Award� From Chicagoland Apartment Association - Company Cited for �Best Corporate Housing Program� - Corporate News - October 7, 2003" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B1FA-2B3C-A4E6-6ECC-5452707B7C2F" title="BridgeStreet Worldwide Sweeps Corporate Housing 'Tower of Excellence' Awards" description="BridgeStreet Worldwide Sweeps Corporate Housing 'Tower of Excellence' Awards - Named Corporate Housing Provider of the Year-Company, Most Creative Marketing, Volunteer of the Year and Provider of the Year-Individual - Corporate News - February 24, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B3B0-2B3C-A4E6-6C80-5726A19B1D1A" title="BridgeStreet Announces Innovative Licensing Program, First in the Corporate Housing Industry" description="BridgeStreet Announces Innovative Licensing Program, First in the Corporate Housing Industry - Program Provides Regional Corporate Housing Companies with Global Reach - Corporate News - April 29, 2002" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B258-2B3C-A4E6-6E56-67E3945C46FC" title="BridgeStreet Worldwide Named to Manage Serviced Apartments at Heathrow Airport" description="BridgeStreet Worldwide Named to Manage Serviced Apartments at Heathrow Airport - Berkeley Park at Heathrow Airport Adds Key Location to BridgeStreet Inventory - Corporate News - October 6, 2004" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B239-2B3C-A4E6-6AA5-6F3B6EE62301" title="BridgeStreet Chicago Office Receives Third Consecutive " description="BridgeStreet Chicago Office Receives Third Consecutive - Company Cited for Best Corporate Housing Program - Corporate News - November 16, 2004" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=50C83AAB-76F1-4BB9-88BA-720A116286BA&instance=4" title="BridgeStreet Worldwide Surpasses 100 Market Milestone" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Corporate News - September 7, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B277-2B3C-A4E6-6EBF-7DFAAA9E6920" title="BridgeStreet Worldwide�s Global Partner Program Expands to Seven Additional Markets" description="BridgeStreet Worldwide�s Global Partner Program Expands to Seven Additional Markets - New Toronto-Based Partner Largest Corporate Housing Provider in Canada - Corporate News - September 9, 2004" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B2D5-2B3C-A4E6-619B-8D19BDADD24E" title="BridgeStreet Corporate Housing Adds Six Partners to Global Licensing Program" description="BridgeStreet Corporate Housing Adds Six Partners to Global Licensing Program - Program Now Has 14 Partners, Representing 16 Markets - Corporate News - December 9, 2003" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B1DB-2B3C-A4E6-6C72-91B88C499D7E" title="BridgeStreet Worldwide Announces Annual BridgeCare Award Winner" description="BridgeStreet Worldwide Announces Annual BridgeCare Award Winner - Exceptional Customer Service Programs Are The Foundation to 'Making Corporate Housing Easy' - Corporate News - May 3, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B0C2-2B3C-A4E6-649F-932C513A5FF0" title="BridgeStreet Worldwide Acquires Twelve Oaks Corporate Housing in Chicago" description="BridgeStreet Worldwide Acquires Twelve Oaks Corporate Housing in Chicago - BridgeStreet Becomes One of the Windy City's Largest Furnished Apartment Providers - Corporate News - February 23, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B100-2B3C-A4E6-68EE-98F88CA1AE79" title="Bridgestreet Worldwide expands into Leeds, England, with the addition of 'Residence 6'" description="Bridgestreet Worldwide expands into Leeds, England, with the addition of 'Residence 6' - Brand New Luxury Serviced Apartments Opening in Leeds City Centre in April 2006 - Corporate News - December 15, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=485AE18F-2B3C-A4E6-6051-A38982308707" title="BridgeStreet Worldwide and Global Partner, Furnished Quarters, Enter Boston Market " description="BridgeStreet Worldwide and Global Partner, Furnished Quarters, Enter Boston Market - Corporate Housing Available in Nine Boston Locations, Downtown and Suburbs - Corporate News - May 8, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B219-2B3C-A4E6-65C2-BA4CCFEB2F9C" title="BridgeStreet Takes Next Step in �Corporate Housing Made Easy� Mission" description="BridgeStreet Takes Next Step in �Corporate Housing Made Easy� Mission - Unveils New Global Affinity Program to Augment Global Partners - Corporate News - January 20, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B15E-2B3C-A4E6-66F2-BC69BEB0B1E3" title="BridgeStreet Worldwide Partners with Rebuilding Together to Help Less Fortunate" description="BridgeStreet Worldwide Partners with Rebuilding Together to Help Less Fortunate - BridgeStreet Associates, Global Partners, Vendor Partners Restore Home - Corporate News - October 13, 2005" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B371-2B3C-A4E6-6068-C1F3077D51ED" title="BridgeStreet Announces Strategic Alliance With Global Home Network" description="BridgeStreet Announces Strategic Alliance With Global Home Network - Combined Housing Inventory Extends to 135 Markets in More Than 40 Countries - Corporate News - June 18, 2002" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=75B64AC5-22AD-4DFF-A067-D630E949D723" title="New Orleans to be Flooded Again-Not with Water, But with Bedding and Housewares from BridgeStreet" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Corporate News - August 28, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B2B6-2B3C-A4E6-6219-DB3879E4083B" title="BridgeStreet Corporate Housing Adds Five Partners to Global Licensing Program" description="BridgeStreet Corporate Housing Adds Five Partners to Global Licensing Program - - Corporate News - January 30, 2004" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=485AE1EC-2B3C-A4E6-61EE-E9BDC5C11807" title="BridgeStreet Worldwide Wins Pair of Corporate Housing Awards" description="BridgeStreet Worldwide Wins Pair of Corporate Housing - Lee Curtis, BridgeStreet President, Named Individual Provider Member of the Year - Corporate News - March 27, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B296-2B3C-A4E6-60F3-EC68EE9356E8" title="BridgeStreet Sets Plan to Transform Corporate Housing Experience" description="BridgeStreet Sets Plan to Transform Corporate Housing Experience - Announces New Brand Mission: �Corporate Housing Made Easy� - Corporate News - August 12, 2004" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=75ED1979-5BE0-481E-A3AF-EF06D1254EB9" title="BridgeStreet Worldwide Announces 2005 BridgeCare Award Winner" description="BridgeStreet Worldwide Announces 2005 BridgeCare Award Winner - Heather Linton Cited for Exceptional Support - Corporate News - July 11, 2006" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B390-2B3C-A4E6-6400-FA0765B40496" title="BridgeStreet Is First to Place Full Corporate Housing Inventory on GDS" description="BridgeStreet Is First to Place Full Corporate Housing Inventory on GDS - Online Access Opens Up New Commission Opportunities for Travel Agents - Corporate News - June 7, 2002" /> <siteMapNode url="~/Apps/Announcements.aspx?CM=D596B13F-2B3C-A4E6-6D47-FCB993A8BD49" title="BridgeStreet Worldwide Chicago Office Receives Record-Setting Fourth Consecutive �CAMME� Award from Chicagoland Apartment Association" description="BridgeStreet Worldwide Chicago Office Receives Record-Setting Fourth Consecutive �CAMME� Award from Chicagoland Apartment Association - Company Also Wins �Associate Member Brochure�Print Form� Category - Corporate News - October 20, 2005" /> </siteMapNode> <siteMapNode url="~/Apps/ContactUs.aspx?CM=4F38AB79-0BFE-4DB5-9C19-1D056C287808&instance=3" title="Nous Contacter" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Nous Contacter" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=690CC898-F538-4772-A127-CCBE5FB2742B" title="Mon Compte" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Mon Compte"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=83450851-3BB8-4BA4-BA99-2E5FB30F374D" title="BridgeStreet Concierge" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - BridgeStreet Concierge" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=1EBB6235-4174-437C-B23F-31B9E58E4FC0&instance=2" title="Programme D�h�bergement" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Solutions de Logement"> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=0612F0E6-B3E1-43A1-8EBC-7CB36EC70D06" title="�tudes de Cas" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - �tudes de Cas" /> </siteMapNode> <siteMapNode url="~/Apps/ContactUs.aspx?CM=4F38AB79-0BFE-4DB5-9C19-1D056C287808" title="Nous Contacter" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Nous Contacter" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=077BF590-4317-431A-8E25-14134B86C6E4" title="Politique de Confidentialit�" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Politique de Confidentialit�" /> </siteMapNode> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=00D1CBCB-48B2-4033-86F4-258EA017997F" title="Informations Sp�ciales" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Informations Sp�ciales"> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=E255629F-6C1A-4DA3-BAD7-03E3525B4EBE" title="Pittsburgh, Pennsylvania" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=B83CDF29-C898-499E-B283-11BE1D79E30C" title="San Francisco, California" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=9D3AA335-3EAD-40F5-83AE-4E050B05BD90" title="Cincinnati, Ohio" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=49D950E2-4546-4E03-8D6E-5B289197CE88" title="Baltimore, Maryland" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=A32BF3CF-37E5-454B-A082-61232CD188C1" title="Chicago, Illinois" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=E13EB281-55E3-4534-A01E-9C5EE809C533" title="Raleigh-Durham, North Carolina" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=3A9C2648-272A-4739-9087-AF48F6F733BE" title="Columbus, Ohio" description="Buckeye Bargains!!<br>Come see what Central Ohio has to offer! - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=A0E61114-3641-41EC-8B4A-B7DCFF322529" title="Arlington, Virginia" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=60CC7B85-DE8F-44AE-B4F4-BCF1DCA7D5EA" title="Houston, Texas" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=7A8E4B27-66F8-4F31-BAAA-C0EE2E07E418" title="Cleveland, Ohio" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=DD5BCDAE-988F-4947-AEA3-C57EAD13EF24" title="Memphis, Tennessee" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=68394FB5-28B8-4ED8-999B-D0A90D324C70" title="Paris, France" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=F2E42AA9-FD3B-4A73-95AB-D9D427E85269" title="Just Outside New York City" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> <siteMapNode url="~/Apps/WebSpecials.aspx?CM=B4B978F7-DEC2-47AA-BEAA-F64BB70096F5" title="St. Louis, Missouri" description="Temporary housing for extended stays by BridgeStreet Worldwide corporate housing and serviced apartments. - Area Special" /> </siteMapNode> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=A432E443-F244-47AD-B074-D401F72B9A64" title="Flash Info" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Travail dur... Sommeil facile."> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=3EC5E979-3916-43B0-B935-40895D8CBDA1" title="Saint Germain" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Saint Germain" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=C4B7E9ED-0E8C-494E-9BDF-5BACDE299E47" title="Bridgestreet l'Opera" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Bridgestreet l'Opera" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=78CE103E-E412-4AF9-9487-87B2731ED127" title="Postbox-Vie De Ville" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Postbox-Vie De Ville" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=CD34C2F8-F97D-4AB3-BF32-8E7D88722D77" title="Reconstruction Ensemble" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Reconstruction Ensemble" /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=A432E443-F244-47AD-B074-D401F72B9A64&instance=1" title="Travail dur... Sommeil facile." description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Travail dur... Sommeil facile." /> <siteMapNode url="~/Apps/CMSTemplate.aspx?CM=0E7F3F43-4416-491D-9A0C-D968D7E101B6" title="Notting Hill" description="Solutions d'h�bergement en appartements meubl�s avec services h�teliers pour tous types de s�jour : courts s�jours, s�jours professionnels, d�menagement, relocation - Flash Info - Notting Hill" /> </siteMapNode> </siteMapNode> </siteMap>
Now all you have to do is take your XSLT and embed it in your dll. Add a new XSLT file to your class library project. Simply copy the XSLT code from above (or the downloaded source code). Once it's in your project, you can add it as a resource file to be extracted from the assembly as a byte[] (can we say, no extraneous disk reads?).
On the class library property pages, select �Resources� tab. Add your file to the resources. Select the newly added XSLT file. Set the properties as such (FileType must be Binary):

Go to the file in the solution explorer, select the file you need to embed, and change the build action to Embedded Resource:

Now when your class library builds, you can access the file in memory without using System.Reflection like in .NET 1.1.
Step 5: Backup and Write Methods
Now you need to write the code that will backup existing files, perform the transformation, and write the *.sitemap file.
First the backup method. I only wanted a daily backup so I named my files accordingly. You can change the ToString("") Date format info if you want a different scheme. /// Backs up the current Sitemap file to a user defined directory /// /// The relative path to the directory where the current files are stored /// The relative path to the directory where the sitemap backup files are stored public void BackupCurrentFile(string pathToCurrent, string pathToBackup) { string fullCurrentPath = HttpContext.Current.Server.MapPath(pathToCurrent); string fullBackupPath = HttpContext.Current.Server.MapPath(pathToBackup); if (Path.HasExtension(fullCurrentPath) || Path.HasExtension(fullBackupPath)) { throw new ArgumentException("You cannot specify the fileName of your sitemap, please provide only the directories where you'd like to store your sitemap files.", "pathToCurrent or pathToBackup"); } else { if (!fullCurrentPath.EndsWith("\\")) { fullCurrentPath = fullCurrentPath + "\\"; } if (!fullBackupPath.EndsWith("\\")) { fullBackupPath = fullBackupPath + "\\"; } fullCurrentPath = fullCurrentPath + this.FileName; string backupFileName = Path.GetFileNameWithoutExtension(fullBackupPath + this.FileName); backupFileName = backupFileName + "_" + DateTime.Now.ToString("yyyy-MM-dd") + ".sitemap"; fullBackupPath = fullBackupPath + backupFileName; FileInfo siteMapFileInfo = new FileInfo(fullCurrentPath); FileInfo backupFileInfo = new FileInfo(fullBackupPath); if (backupFileInfo.Exists) { File.SetAttributes(fullBackupPath, FileAttributes.Normal); } siteMapFileInfo.CopyTo(fullBackupPath, true); } }
And now the moment you've been waiting for, the transformation and file write. You can see that the xslt is pulled out of memory as a byte[]. This is a smokin' fast technique with minimal code. Note that SiteMapTransformer is the name of the embedded resource (the xslt file). Also note that the DataSet.GetXML() method generates the preceding input schema right from the dataset.
/// Writes the Sitemap to a standard ASP.NET sitemap xml file /// /// The relative path name where you want to write to /// The relative path to the directory where the current sitemap files are stored /// The relative path to the directory where the sitemap backup files are stored public void WriteFile(string pathToCurrent, string pathToBackup) { string fullPath = HttpContext.Current.Server.MapPath(pathToCurrent); if (Path.HasExtension(fullPath)) { throw new ArgumentException("You cannot specify the fileName of your sitemap, please provide only the directory you'd like to store your sitemap files.", "pathToCurrent"); } else { if (!fullPath.EndsWith("\\")) { fullPath = fullPath + "\\"; } FileInfo siteMapFileInfo = new FileInfo(fullPath + this.FileName); //Backup the current file if (siteMapFileInfo.Exists) { this.BackupCurrentFile(pathToCurrent, pathToBackup); } XslCompiledTransform xslt = new XslCompiledTransform(); MemoryStream transformStream = new MemoryStream(Resources.SiteMapTransformer); MemoryStream siteMapXmlStream = new MemoryStream(); //Generate Xml from the heirarchical dataset XmlReader dataSetXmlReader = XmlReader.Create(new StringReader(this.DataSource.GetXml())); //Load the Xslt file from the assembly manifest (now in a stream) xslt.Load(XmlReader.Create(transformStream)); //Perform the transformation xslt.Transform(dataSetXmlReader, XmlWriter.Create(siteMapXmlStream)); //Write the stream to file, the current file is overwritten if it exists if (siteMapFileInfo.Exists) { File.SetAttributes(siteMapFileInfo.FullName, FileAttributes.Normal); } File.WriteAllBytes(siteMapFileInfo.FullName, siteMapXmlStream.ToArray()); } }
Step 6: Use it
We had to define different sitemap providers in our web.config based on the different types of sitemaps we had. Here are the Web.Config requirements: <siteMap defaultProvider="Content_en-US" enabled="true"> <providers> <clear/> <add name="Content_en-US" description="The SiteMap Provider for en-US Content" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="/Website/SiteMaps/Content_en-US.sitemap" /> <add name="Content_en-GB" description="The SiteMap Provider for en-GB Content" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="/Website/SiteMaps/Content_en-GB.sitemap" /> <add name="Content_fr-FR" description="The SiteMap Provider for fr-FR Content" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="/Website/SiteMaps/Content_fr-FR.sitemap" /> <add name="Properties_en-US" description="The SiteMap Provider for en-US Properties" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="/Website/SiteMaps/Properties_en-US.sitemap" /> <add name="Properties_en-GB" description="The SiteMap Provider for en-GB Properties" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="/Website/SiteMaps/Content_en-GB.sitemap" /> <add name="Properties_fr-FR" description="The SiteMap Provider for fr-FR Properties" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="/Website/SiteMaps/Properties_fr-FR.sitemap" /> </providers> </siteMap>
Now your front end code is really simple: <asp:Label ID="SC_Title" runat="server" SkinID="MainContentHeader" meta:resourcekey="SC_TitleResource1"></asp:Label><br /> <br /> <asp:Label ID="SC_ContentSitemapLabel" SkinID="MainContentSubHeader" runat="server" meta:resourcekey="SC_ContentSitemapLabelResource1"></asp:Label><br /><br /> <asp:TreeView SkinID="SiteMapTree" ID="SC_ContentTree" DataSourceID="SC_SiteMapSource" runat="server" meta:resourcekey="SC_ContentTreeResource1"> </asp:TreeView><br /><br /> <asp:Label ID="SC_PropertySitemapLabel" SkinID="MainContentSubHeader" runat="server" meta:resourcekey="SC_PropertySitemapLabelResource1"></asp:Label><br /><br /> <asp:SiteMapDataSource ID="SC_SiteMapSource" runat="server" />
Code Behind example on page_load (expand depth is how many tiers you want the intial view to be expanded to:
string currentCultureName = Session["CurrentCulture"] as string; this.SC_SiteMapSource.SiteMapProvider = "Content_" + currentCultureName; this.SC_ContentTree.ExpandDepth = 1;
Now you have your sitemap, but what about breadcrumbs? Here you go:
<asp:SiteMapPath SkinID="SiteMapPath" ID="SC_SiteMapPath" runat="server"> </asp:SiteMapPath>
On the MasterPage:
public void SetSiteMapProvider(string siteMapProviderName) { this.SC_SiteMapPath.SiteMapProvider = siteMapProviderName; }
On the Web Form: this.Master.SetSiteMapProvider("Content_" + currentCulture);
You get the idea. Consuming the *.sitemap file is quite trivial.
Step 7: Scheduling your SiteMap File Generation
This is where one size does not fit all. You have the class with methods that can do the job now. There are three approaches I can recommend to scheduling the sitemap file write & backup: Windows Service, a Win Forms App running on a scheduled task server, or in .NET 3.0 there are some cool things in the WWF (that is Workflow Foundation, not wrestling). Until MS is RTM for 3.0 I would stick with a Windows Service. That is outside the scope of this article, but the code that does the job is also quite trivial. You just get a handle on the HttpContext for the website of interest, and then use the class as such: CMSSitemap sitemap = new CMSSitemap(new Guid("YOURAPPGUID"), "fr-FR", SitemapType.Content); sitemap.WriteFile("/Website/SiteMaps", "/Website/SiteMapBackup");
The ideal solution which you're probably screaming at me right now is to trigger this code block when you know for sure that content has been deleted or inserted into your Dynamic CMS. If you have power over this, DO IT! Remember, if you have a CMS that is this big, and one of your primary goals is to index your content on search engines that you want the SiteMap to reflect the content that is most up to date for both browsing and search engine indexing purposes. If you're using the ASP.NET TreeView control, feel safe that Lynx can see it. Lynx could not see our links in an Infragistics TreeMenu control. Whatever controls you're using for your Presentation Layer, be sure that a text-based browser like Lynx can see the links if you're concerned about search engine indexing.
Also note that there's still some fuzziness surrounding whether search engines will index query string urls. I see that Google tries to index mine in my google webmaster account. You may want to be sure of this and put some effort into it. If you need to take it a step further, try search engine safe (SES) urls. This guy did a fantastic job on an HttpModule for that: http://weblogs.asp.net/skillet/archive/2004/05/04/125523.aspx
Final Comments
You achieved a lot with your implementation. Having a sitemap for a dynamic system is not a small task. ASP.NET 2.0 takes the edge off the "VIEW" code. You don't want to rip through your CMS database for each webrequest, but you want to take advantage of the built in .NET 2.0 SiteMap controls and providers. Your SiteMap XMLs do a lot, they branch your content into different sitemap types which can be context sensitive, can support multiple languages, and shine your shoes while they're at it. The XSLT takes your dataset straight to *.sitemap xml format in a few lines of code. Embedding it in the class library as a byte[] and consuming it as a MemoryStream is way cool.
You saw the Breadcrumbs up at the top of the article. Here is a view of the sitemap tree:

You can see that our CMS has defined content types that we would like to isolate for usability. Having multiple sitemap types allows us to feed off of this excellent architectural feature.
You can see this and more on http://www.bridgestreet.com/Apps/Sitemap.aspx. Non-relational flat, UI data is multilingual via Satellite assemblies. Relational data that runs the menus and content is cached in Server memory as custom objects, which makes the site incredibly fast because not a single element is static on any page, yet very few DB trips ever have to be made. My other geographic article is implemented on that site too. You can see its power on the website.
I hope that you can use these concepts and adapt them to your advantage in your own Sitemap endeavors. |