We have a website that uses a standard default site map, with protection:
<siteMap defaultProvider="default" enabled="true"> <providers> <add siteMapFile="~/Web.sitemap" securityTrimmingEnabled="true" name="default" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </providers> </siteMap>
Everything is very good, but there was a request to change the Title one node based on some initial criteria. It sounds simple, but apparently not.
Attempt 1 - handling the SiteMapResolve event. It does not seem to matter where this event is handled, I showed it in Global.asax simply because it was one of the places where I tried it and it worked.
Public Class Global_asax Inherits System.Web.HttpApplication Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs) AddHandler SiteMap.SiteMapResolve, AddressOf SiteMapResolve End Sub Sub Application_EndRequest(ByVal sender As Object, ByVal e As EventArgs) RemoveHandler SiteMap.SiteMapResolve, AddressOf SiteMapResolve End Sub Private Shared Function SiteMapResolve(ByVal sender As Object, ByVal e As SiteMapResolveEventArgs) As SiteMapNode Dim node As SiteMapNode = SiteMap.CurrentNode If IsThisTheNodeToChange(node) Then node = node.Clone() node.Title = GetNodeTitle() End If Return node End Function End Class
This worked fine when the corresponding page was moved, but unfortunately, part of the site navigation includes a combo box tied to the site map, like this:
<asp:SiteMapDataSource ID="siteMapDataSource" runat="Server" ShowStartingNode="false" StartFromCurrentNode="false" StartingNodeOffset="1" /> <asp:DropDownList ID="pageMenu" runat="Server" AutoPostBack="True" DataSourceID="siteMapDataSource" DataTextField="Title" DataValueField="Url" />
When this menu is displayed, the SiteMapResolve event SiteMapResolve not triggered for any content because the current node is the page on which the menu is defined. As a result, the menu displays the name of the meaningless placeholder from the physical site map file, and not the correct name.
Attempt 2 . Writing your own Sitemap provider. I did not want to duplicate all the default behavior, so I tried to get the following from the default provider.
Public Class DynamicXmlSiteMapProvider Inherits XmlSiteMapProvider Private _dataFixedUp As Boolean = False Public Overrides Function GetChildNodes(ByVal node As SiteMapNode) As SiteMapNodeCollection Dim result As SiteMapNodeCollection = MyBase.GetChildNodes(node) If Not _dataFixedUp Then For Each childNode As SiteMapNode In result FixUpNode(childNode) Next End If Return result End Function Private Sub FixUpNode(ByVal node As SiteMapNode) If IsThisTheNodeToChange(node) Then node.ReadOnly = False node.Title = GetNodeTitle() node.ReadOnly = True _dataFixedUp = True End If End Sub End Class
This does not work, because GetChildNodes does not appear very often when navigating a site.
Attempt 3 . Try to correct the data immediately after loading it into memory, and not when accessing it.
Public Class DynamicXmlSiteMapProvider Inherits XmlSiteMapProvider Private _dataFixInProgress As Boolean = False Private _dataFixDone As Boolean = False Public Overrides Function BuildSiteMap() As SiteMapNode Dim result As SiteMapNode = MyBase.BuildSiteMap() If Not _dataFixInProgress AndAlso Not _dataFixDone Then _dataFixInProgress = True For Each childNode As SiteMapNode In result.GetAllNodes() FixUpNode(childNode) Next _dataFixInProgress = False _dataFixDone = True End If Return result End Function Private Sub FixUpNode(ByVal node As SiteMapNode) If IsThisTheNodeToChange(node) Then node.ReadOnly = False node.Title = GetNodeTitle() node.ReadOnly = True End If End Sub End Class
It works. However, I am worried about calling GetAllNodes in the BuildSiteMap method. It just seems wrong to me to recursively pull all the data into memory in order to fix one value. In addition, I cannot control when the BuildSiteMap is BuildSiteMap . I would prefer something more than "Attempt 1", which is called on demand, when node data is first required.
Attempting 4 (NEW) - as Run 2 but overriding all virtual elements that are associated with reading data ( CurrentNode , FindSiteMapNode , FindSiteMapNodeFromKey , GetChildNodes , GetCurrentNodeAndHintAncestorNodes , GetCurrentNodeAndHintNeighborhoodNodes , GetParentNode , GetParentNodeRelativeToCurrentNodeAndHintDownFromParent , GetParentNodeRelativeToNodeAndHintDownFromParent , HintAncestorNodes , HintNeighborhoodNodes ), to try to intercept the reading dynamic node somewhere.
This did not work. I am adding debugging instructions in all overridden elements, and it seems that none of them are called at all when the data is bound to a drop-down list. The only explanation I can think of is that all nodes are all read into memory at one time during a call to BuildSiteMap , so SiteMapNode does not fall into the provider class when enumerating child nodes.
Does anyone have any better suggestions?