Posted by Alec on Wed, 15 Jul 2009, in ASP.NET C#
Having a working sitemap in an ASP.NET application can't be anymore important. You can use it for navigations, breadcrumbs, etc. The default web.sitemap file is an XML file which provides the root node of the site, the SiteMapProvider used, and tracks the provider objects. System.Web.SiteMap inherits from System.Object. The default provider XMLSiteMapProvider works through the Web.sitemap. Besides being useful for site navigation, the sitemap also provides a easy way for search engines to look for pages on the site.
However, hard coding these urls in the sitemap file isn't a solution for dynamic website, for example one that is managed by a content management system. Pages are constantly generated. So, the sitemap would have to be updated everytime we create a new page or a new url is created. To do that, we could use a dynamic sitemap provider:
public class DynamicSiteMapProvider : StaticSiteMapProvider { public DynamicSiteMapProvider() : base() { } private String _siteMapFileName; private SiteMapNode _rootNode = null; // Return the root node of the current site map. public override SiteMapNode RootNode { get { return BuildSiteMap(); } } /// <summary> /// Pull out the filename of the site map xml. /// </summary> /// <param name="name"></param> /// <param name="attributes"></param> public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes) { base.Initialize(name, attributes); _siteMapFileName = "web.sitemap"; } private const String SiteMapNodeName = "siteMapNode"; public override SiteMapNode BuildSiteMap() { lock (this) { if (null == _rootNode) { Clear(); // Load the sitemap's xml from the file. XmlDocument siteMapXml = LoadSiteMapXml(); // Create the first site map item from the top node in the xml. XmlElement rootElement = (XmlElement)siteMapXml.GetElementsByTagName( SiteMapNodeName)[0]; // This is the key method - add the dynamic nodes to the xml AddDynamicNodes(rootElement); // Now build up the site map structure from the xml GenerateSiteMapNodes(rootElement); } } return _rootNode; } /// <summary> /// Open the site map file as an xml document. /// </summary> /// <returns>The contents of the site map file.</returns> private XmlDocument LoadSiteMapXml() { XmlDocument siteMapXml = new XmlDocument(); siteMapXml.Load(AppDomain.CurrentDomain.BaseDirectory + _siteMapFileName); return siteMapXml; } /// <summary> /// Creates the site map nodes from the root of /// the xml document. /// </summary> /// <param name="rootElement">The top-level sitemap element from the XmlDocument loaded with the site map xml.</param> private void GenerateSiteMapNodes(XmlElement rootElement) { _rootNode = GetSiteMapNodeFromElement(rootElement); AddNode(_rootNode); CreateChildNodes(rootElement, _rootNode); } /// <summary> /// Recursive method! This finds all the site map elements /// under the current element, and creates a SiteMapNode for /// them. On each of these, it calls this method again to /// create it's new children, and so on. /// </summary> /// <param name="parentElement">The xml element to iterate through.</param> /// <param name="parentNode">The site map node to add the new children to.</param> private void CreateChildNodes(XmlElement parentElement, SiteMapNode parentNode) { foreach (XmlNode xmlElement in parentElement.ChildNodes) { if (xmlElement.Name == SiteMapNodeName) { SiteMapNode childNode = GetSiteMapNodeFromElement((XmlElement)xmlElement); AddNode(childNode, parentNode); CreateChildNodes((XmlElement)xmlElement, childNode); } } } /// <summary> /// The key method. You can add your own code in here /// to add xml nodes to the structure, from a /// database, file on disk, or just from code. /// To keep the example short, I'm just adding from code. /// </summary> /// <param name="rootElement"></param> private void AddDynamicNodes(XmlElement rootElement) { DataTable dtt = DatabaseFactory.CreateDatabase().ExecuteDataSet("GetPages").Tables[0]; if (dtt.Rows.Count > 0) { //section level foreach (DataRow dtrSection in dtt.Rows) { XmlElement xeSection = AddDynamicChildElement(rootElement, dtrSection["location"].ToString(), dtrSection["title"].ToString(), ""); DataTable dttPages = getChilds(Convert.ToInt16(dtrSection["id"].ToString())); if (dttPages.Rows.Count > 0) { //page level foreach (DataRow dtrPage in dttPages.Rows) { XmlElement xePage = AddDynamicChildElement(xeSection, dtrPage["location"].ToString(), dtrPage["title"].ToString(), ""); DataTable dttSubPages = getChilds(Convert.ToInt16(dtrPage["id"].ToString())); if (dttSubPages.Rows.Count > 0) { //subpage level foreach (DataRow dtrSubpage in dttSubPages.Rows) { XmlElement xeSubPage = AddDynamicChildElement(xePage, dtrSubpage["location"].ToString(), dtrSubpage["title"].ToString(), ""); DataTable dttGrandPages = getChilds(Convert.ToInt16(dtrSubpage["id"].ToString())); if (dttGrandPages.Rows.Count > 0) { //grand page level foreach (DataRow dtrGrandPage in dttGrandPages.Rows) { AddDynamicChildElement(xeSubPage, dtrGrandPage["location"].ToString(), dtrGrandPage["title"].ToString(), ""); } } } } } } } } } private DataTable getChilds(int intPageId) { DataTable dttChilds = DatabaseFactory.CreateDatabase().ExecuteDataSet("GetPageByParentId", intPageId).Tables[0]; return dttChilds; } private static XmlElement AddDynamicChildElement(XmlElement parentElement, String url, String title, String description) { // Create new element from the parameters XmlElement childElement = parentElement.OwnerDocument.CreateElement(SiteMapNodeName); childElement.SetAttribute("url", url); childElement.SetAttribute("title", title); childElement.SetAttribute("description", description); // Add it to the parent parentElement.AppendChild(childElement); return childElement; } private SiteMapNode GetSiteMapNodeFromElement(XmlElement rootElement) { SiteMapNode newSiteMapNode; String url = rootElement.GetAttribute("url"); String title = rootElement.GetAttribute("title"); String description = rootElement.GetAttribute("description"); // The key needs to be unique, so hash the url and title. newSiteMapNode = new SiteMapNode(this,(url + title).GetHashCode().ToString(), url, title, description); return newSiteMapNode; } protected override SiteMapNode GetRootNodeCore() { return RootNode; } // Empty out the existing items. protected override void Clear() { lock (this) { _rootNode = null; base.Clear(); } }
Now that we have a dynamic sitemap, we could have our breadcrumb using it, thus generating up to dated breadcrumb menu: <asp:SiteMapPath SiteMapProvider="MyCustomSiteMap" ID="smpMaster" runat="server" PathSeparator="&nbsp; >>&nbsp; " ParentLevelsDisplayed="5" PathDirection="RootToCurrent" RenderCurrentNodeAsLink="false" ShowToolTips="true" CssClass="breadcrumbs"> </asp:SiteMapPath>
However, there's one problem, which is caching as it uses aggressive caching, and so, some changes wont be visible unless there has been a file change in your application or someone touched web.config.
Any idea how to resolve?