Dynamically change the name of SiteMapNode

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?

+4
source share
4 answers

In our Custom SiteMapProvider, we override the BuildSiteMap method and create SiteMapNodes manually. To change and / or add custom properties, we add custom attributes to SiteMapNodes by creating a NameValueCollection and add it to the SiteMapNode constructor.

+2
source

You are very close to attempt # 2 - you just need to override GetParentNode and FindSiteMapNode .

+1
source

Thanks to Mark and Rex for the suggestions. What I ended up with was to leave the sitemap provider alone and just commit one node to the main page like this:

 Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init ' Do this as early as possible in the page lifecycle so that it happens before any ' automatic data binding or control initialisation is done. Dim node As SiteMapNode = GetNodeToEdit() Dim nodeReadOnly As Boolean = node.ReadOnly node.ReadOnly = False node.Title = GetNodeTitle() node.ReadOnly = nodeReadOnly End Sub 

However, I accepted Mark's answer because, as I will, if it turns out that more extensive changes are needed in the future.

+1
source

Thank you Christian for your research. Using your results, I came up with the following that might help others:

 ' Dynamically update some menu items. ' ' Since the correct Medical Center ID is not known until runtime, need to ' append "&MEDICAL_CENTER_ID=xxx" to all of the report URLs in Web.sitemap. Public Shared Sub UpdateMenu(ByVal MedicalCenterID As String) Dim CurrentNodeTitle As String = "" Dim NodeReadOnlyProperty As Boolean ' Home menu item CurrentNodeTitle = SiteMap.CurrentNode.Title ' Determines if the current node has child nodes. If (SiteMap.CurrentNode.HasChildNodes) Then ' Loop through top level 1 menu items (looking for Reports) For Each ChildNodesEnumerator1 As SiteMapNode In SiteMap.CurrentNode.ChildNodes CurrentNodeTitle = ChildNodesEnumerator1.Title If CurrentNodeTitle = "Reports" Then ' Loop through level 2 menu items (looking for specfic reports) For Each ChildNodesEnumerator2 As SiteMapNode In ChildNodesEnumerator1.ChildNodes CurrentNodeTitle = ChildNodesEnumerator2.Title If CurrentNodeTitle = "Multi-Day Vehicle Requests" Or _ CurrentNodeTitle = "XXXXXXXXXXXXXXXXX" Then ' First check if the URL has not been modified already If InStr(ChildNodesEnumerator2.Url, "MEDICAL_CENTER_ID") = 0 Then NodeReadOnlyProperty = ChildNodesEnumerator2.ReadOnly ChildNodesEnumerator2.ReadOnly = False ChildNodesEnumerator2.Url = ChildNodesEnumerator2.Url & "&MEDICAL_CENTER_ID=" & MedicalCenterID ChildNodesEnumerator2.ReadOnly = NodeReadOnlyProperty End If End If Next End If Next End If End Sub 
+1
source

All Articles