-[Danny Chen]- Blog of an ASP.NET QA tester

Tips and info about Site Navigation, ImageMap, Menu and other cool ASP.NET v2.0 features.

March 2005 - Posts

Binding to custom web.sitemap attributes

We've had various feature requests come in for the site navigation feature to include additional built in properties.  This got turned down because it wasn't really useful data about a Site Map Node and because it was easily done through the extensibility features.  In general this will work for any custom attribute but I'll show examples for target since it's the most commonly requested.

First, lets start with a simple web.sitemap and add a custom attributes that we'll want to map to target.  Web.sitemap allows for custom attributes to be added to each SiteMapNode.  They can be accessed later by the indexer property, for example:

Dim someNode As SiteMapNode = GetMyNode()
Dim value As String = someNode("value")
 
or
 
SiteMapNode someNode = GetMyNode();
string value = someNode["value"];

Here's an example web.sitemap:

 
<?xml version="1.0" encoding="utf-8" ?>
<
siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <
siteMapNode url="Home.aspx" title="Home"  target="_blank">
    <
siteMapNode url="Work.aspx" title="Work"  target="_bottom" />
    <
siteMapNode url="School.aspx" title="School"  target="_top" />
  </
siteMapNode>
</
siteMap>


Now add a menu or treeview to the page bound to the SiteMapDataSource.  In order to make this work, we'll subscribe to the Item/Node databound event.

<%@ Page Language="VB" AutoEventWireup="true"  
        
CodeFile="Default.aspx.vb"
        
Inherits="_Default" EnableViewState="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<
html xmlns="http://www.w3.org/1999/xhtml" >
<
head id="Head1" runat="server">
    <title>My Page</title>
</
head>
<
body>
    <form id="form1" runat="server">
        <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
        <asp:Menu ID="Menu1" runat="server"
        
DataSourceID="SiteMapDataSource1"      
        
OnMenuItemDataBound="Menu1_MenuItemDataBound">
        </asp:Menu>
        <asp:TreeView ID="TreeView1" runat="server"
        
DataSourceID="SiteMapDataSource1"
        
OnTreeNodeDataBound="TreeView1_TreeNodeDataBound">
        </asp:TreeView>
    </form>
</
body>
</
html>

 

And here is the code that makes it work:

Partial Class Default
    Inherits System.Web.UI.Page
    
Protected Sub TreeView1_TreeNodeDataBound(ByVal sender As Object, _
      
ByVal e As System.Web.UI.WebControls.TreeNodeEventArgs) _
      
Handles TreeView1.TreeNodeDataBound

        e.Node.Target =
CType(e.Node.DataItem, SiteMapNode)("target")
    
End Sub

    Protected Sub Menu1_MenuItemDataBound(ByVal sender As Object, _
      
ByVal e As System.Web.UI.WebControls.MenuEventArgs) _
      
Handles Menu1.MenuItemDataBound
        e.Item.Target =
CType(e.Item.DataItem, SiteMapNode)("target")
    
End Sub
End
Class

 

But what about SiteMapPath?  It's less common that this would want to be done for a SiteMapPath but that doesn't mean we shouldn't able to do it.  Lets see...  Well, it does have an itemDataBound event...

    Protected Sub SiteMapPath1_ItemDataBound(ByVal sender As Object, _
  
ByVal e As System.Web.UI.WebControls.SiteMapNodeItemEventArgs) _
  
Handles SiteMapPath1.ItemDataBound

        
If e.Item.ItemType = SiteMapNodeItemType.Parent Or _
           e.Item.ItemType = SiteMapNodeItemType.Root
Then
            If e.Item.Controls(0).GetType().ToString = _
              
"System.Web.UI.WebControls.HyperLink" Then

                CType(e.Item.Controls(0), System.Web.UI.WebControls.HyperLink).Target = _
                 e.Item.SiteMapNode(
"target")
            
End If
        End If
    End Sub

But it isn't very nice.  There are a lot of things to worry about, is the item a parent or root or is the child control actually a link.  I'm not even checking any of the other possible child controls either.  A much better way to do this would be to use the templating.  Notice the databinding against custom attributes using [key].

    <asp:SiteMapPath ID="SiteMapPath1" runat="server">
  <NodeTemplate>
    <asp:HyperLink ID="HyperLink1" runat="server"
          
Target='<%# Eval("[target]") %>'
          
NavigateUrl='<%# Eval("url") %>'
          
ToolTip='<%# Eval("description") %>'>
             <%#Eval("title")%>
    
</asp:HyperLink>
  </NodeTemplate>
</
asp:SiteMapPath>

Note: this is a bare minimum example and it would render the current node as a link as well, a <currentNodeTemplate> would further be needed to duplicate the siteMapPath behavior exactly

More Posts