Alterar dinamicamente o título de um sitemapnode
-
06-07-2019 - |
Pergunta
Temos um site que usa um sitemap padrão padrão de pântanos com o corte de segurança da seguinte maneira:
<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>
Tudo muito bem, mas um pedido chegou para mudar o Title
de um nó com base em alguns critérios de back-end. Parece uma coisa simples, mas aparentemente não.
Tentativa 1 - manuseando o SiteMapResolve
evento. Não parece importar Onde Este evento é tratado, eu mostrei em Global.asax
Apenas porque esse foi um dos lugares que eu tentei e funcionou.
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
Isso funcionou bem quando a página relevante foi navegada, mas infelizmente parte da navegação do site envolve uma caixa de combinação que é ligada a dados para o mapa do site assim:
<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" />
Quando este menu é renderizado, o SiteMapResolve
O evento não dispara para nenhum dos conteúdos, porque o nó atual é a página na qual o menu é definido. Como resultado, o menu mostra o título de espaço reservado sem sentido do arquivo físico do sitemap em vez do título correto.
Tentativa 2 - Escrever meu próprio provedor de mapa do site. Eu não queria duplicar todo o comportamento padrão, então tentei derivar do provedor padrão da seguinte maneira.
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
Isso não funciona porque GetChildNodes
Não parece ser chamado com muita frequência ao navegar pelo site.
Tente 3 - Tente corrigir os dados imediatamente após serem carregados na memória, e não quando são acessados.
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
Isso parece funcionar. No entanto, estou preocupado com a chamada para GetAllNodes
no BuildSiteMap
método. Parece -me errado para mim puxar todos os dados em memória apenas para corrigir um valor. Além disso, não tenho controle sobre quando BuildSiteMap
é chamado. Eu preferiria algo mais parecido com a tentativa 1, que é chamado sob demanda quando os dados do nó forem necessários.
Tentativa 4 (novo) - como a tentativa 2, mas substituindo tudo membros virtuais que têm a ver com dados de leitura (CurrentNode
, FindSiteMapNode
, FindSiteMapNodeFromKey
, GetChildNodes
, GetCurrentNodeAndHintAncestorNodes
, GetCurrentNodeAndHintNeighborhoodNodes
, GetParentNode
, GetParentNodeRelativeToCurrentNodeAndHintDownFromParent
, GetParentNodeRelativeToNodeAndHintDownFromParent
, HintAncestorNodes
, HintNeighborhoodNodes
), para tentar interceptar a leitura do nó dinâmico em algum lugar.
Isso não funcionou. Coloquei declarações de depuração em todos os membros substituídos e parece que nenhum deles é chamado quando a ligação de dados à lista suspensa. A única explicação em que consigo pensar é que todos os nós são todos lidos na memória de uma só vez, durante o BuildSiteMap
ligue para que o SiteMapNode
não está atingindo a classe do fornecedor ao enumerar nós filhos.
Alguém tem alguma sugestão melhor?
Solução
Em nosso sitemaprovidor personalizado, substituímos o método BuildSitemap e construímos os sitemapnodes manualmente. Para alterar e/ou adicionar propriedades personalizadas, adicionamos atributos personalizados aos sitemapnodes, crie um nome NameValuEcollection e adicionei isso ao SITEMAPNODE Constructor.
Outras dicas
Você está bem perto da tentativa #2 - você também precisa substituir GetParentNode e FindsItemapNode também.
Obrigado a Mark e Rex pelas sugestões. O que acabei fazendo foi deixar o provedor do sitemap sozinho e apenas consertar o nó no mestre, assim:
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
No entanto, aceitei a resposta de Mark, porque é assim que farei isso se isso acontece que modificações mais extensas precisam ser feitas no futuro.
Obrigado Christian por sua pesquisa. Usando seus resultados, criei o seguinte que pode ajudar os outros:
' 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's 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