Chapter 18. Using Site Maps

<feature><title>In this Chapter</title> <objective>

Using the SiteMapDataSource Control

</objective>
<objective>

Using the SiteMap Class

</objective>
<objective>

Advanced Site Map Configuration

</objective>
<objective>

Creating Custom Site Map Providers

</objective>
<objective>

Generating a Googler SiteMap File

</objective>
<objective>

Summary

</objective>
</feature>

This chapter jumps into the details of Site Maps. First, you learn how to use the SiteMapDataSource control to represent a Site Map on a page. For example, you learn how to use the SiteMapDataSource control to display a list of all the pages contained in a folder.

Next, you’ll explore the SiteMap and SiteMapNode classes. You learn how to create new Site Map nodes dynamically. You also learn how to programmatically retrieve Site Map nodes and display the properties of a node in a page.

This chapter also examines several advanced features of Site Maps. For example, you learn how to show different Site Maps to different users depending on their roles. You also learn how you can extend Site Maps with custom attributes.

You also learn how to create custom Site Map providers. The first custom Site Map provider—the AutoSiteMapProvider—automatically builds a Site Map based on the folder and page structure of your website. The second custom Site Map provider—the SqlSiteMapProvider—enables you to store a Site Map in a Microsoft SQL Server database table.

Finally, you learn how to generate Google SiteMaps from ASP.NET Site Maps automatically. You can use a Google SiteMap to improve the way that your website is indexed by the Google search engine.

Using the SiteMapDataSource Control

The SiteMapDataSource control enables you to represent a Site Map declaratively in a page. You can bind navigation controls such as the TreeView and Menu controls to a SiteMapDataSource control. You also can bind other controls such as the GridView or DropDownList control to a SiteMapDataSource control.

Imagine, for example, that your website contains the Web.sitemap file in Listing 18.1. Because the default SiteMapProvider is the XmlSiteMapProvider, the SiteMapDataSource control automatically represents the contents of this XML file.

Note

The code samples in this section are located in the SiteMaps application on the CD that accompanies this book.

Example 18.1. Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode
  url="Default.aspx"
  title="Home"
  description="The Home Page">
  <siteMapNode
    url="Products/Default.aspx"
    title="Our Products"
    description="Products that we offer">
    <siteMapNode
      url="Products/FirstProduct.aspx"
      title="First Product"
      description="The description of the First Product" />
    <siteMapNode
      url="Products/SecondProduct.aspx"
      title="Second Product"
      description="The description of the Second Product" />
  </siteMapNode>
  <siteMapNode
    url="Services/Default.aspx"
    title="Our Services"
    description="Services that we offer">
    <siteMapNode
      url="Services/FirstService.aspx"
      title="First Service"
      description="The description of the First Service"
      metaDescription="The first service" />
    <siteMapNode
      url="Services/SecondService.aspx"
      title="Second Service"
      description="The description of the Second Service" />
  </siteMapNode>
</siteMapNode>
</siteMap>

The Site Map file in Listing 18.1 represents a website with the following folder and page structure:

Default.aspx
Products
    FirstProduct.aspx
    SecondProduct.aspx
Services
    FirstService.aspx
    SecondService.aspx

The page in Listing 18.2 illustrates how you can represent a Site Map by binding a TreeView control to the SiteMapDataSource control.

Example 18.2.  Default.aspx

<%@ Page Language="VB" %>
<!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>Home</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:SiteMapPath
        id="SiteMapPath1"
        Runat="server" />

    <hr />

    <asp:TreeView
        id="TreeView1"
        DataSourceID="srcSiteMap"
        Runat="server" />

    <asp:SiteMapDataSource
        id="srcSiteMap"
        Runat="server" />

    </div>
    </form>
</body>
</html>

When you open the page in Listing 18.2, all the elements from the Web.sitemap file are displayed in the TreeView control with the help of the SiteMapDataSource control (see Figure 18.1).

Displaying a Site Map with a TreeView control.

Figure 18.1. Displaying a Site Map with a TreeView control.

Setting SiteMapDataSource Properties

The SiteMapDataSource control includes several valuable properties that you can set to modify the nodes that the control returns:

  • ShowStartingNodeEnables you to hide the starting node.

  • StartFromCurrentNodeEnables you to return all nodes starting from the current node.

  • StartingNodeOffsetEnables you to specify a positive or negative offset from the current node.

  • StartingNodeUrlEnables you to return all nodes, starting at a node associated with a specified URL.

The most useful of these properties is the ShowStartingNode property. Normally, when you display a list of nodes with a Menu or TreeView control, you do not want to display the starting node (the link to the home page). The page in Listing 18.3 illustrates how you can bind a Menu control to a SiteMapDataSource that has the value False assigned to its ShowStartingNode property.

Example 18.3. Services/Default.aspx

<%@ Page Language="VB" %>
<!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">
    <style type="text/css">
        .menuItem
        {
            border:solid 1px black;
            background-color:#eeeeee;
            padding:4px;
            margin:1px 0px;
        }
    </style>
    <title>Our Services</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:SiteMapPath
        id="SiteMapPath1"
        Runat="server" />

    <hr />

    <asp:Menu
        id="Menu1"
        DataSourceID="srcSiteMap"
        StaticMenuItemStyle-CssClass="menuItem"
        DynamicMenuItemStyle-CssClass="menuItem"
        Runat="server" />

    <asp:SiteMapDataSource
        id="srcSiteMap"
        ShowStartingNode="false"
        Runat="server" />

    </div>
    </form>
</body>
</html>

When you open the page in Listing 18.3, only the second-level nodes and descendent nodes are displayed (see Figure 18.2).

Hiding the starting node.

Figure 18.2. Hiding the starting node.

The StartFromCurrentNode property is useful when you want to display a list of all nodes below the current node. For example, the page in Listing 18.4 is the Default.aspx page contained in the Products folder. It displays a list of all the product pages contained in the folder.

Example 18.4. Products/Default.aspx

<%@ Page Language="VB" %>
<!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">
    <style type="text/css">
        html
        {
            font:16px Georgia,Serif;
        }
        .productList li
        {
            margin:5px;
        }
    </style>
    <title>Our Products</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>Products</h1>

    <asp:BulletedList
        id="bltProducts"
        DisplayMode="HyperLink"
        DataTextField="Title"
        DataValueField="Url"
        DataSourceID="srcSiteMap"
        CssClass="productList"
        Runat="server" />

    <asp:SiteMapDataSource
        id="srcSiteMap"
        ShowStartingNode="false"
        StartFromCurrentNode="true"
        Runat="server" />

    </div>
    </form>
</body>
</html>

The page in Listing 18.4 contains a BulletedList control bound to a SiteMapDataSource control. Because the SiteMapDataSource control has its StartFromCurrentNode property set to the value True and its ShowStartingNode property set to the value False, all immediate child nodes of the current node are displayed (see Figure 18.3).

Displaying the contents of a folder.

Figure 18.3. Displaying the contents of a folder.

Using the SiteMap Class

Under the covers, the SiteMapDataSource control represents the contents of the SiteMap class. The SiteMap class represents an application’s Site Map regardless of whether the Site Map is stored in an XML file, a database, or some other data source. The class is a memory-resident representation of Site Map data.

All the properties exposed by the SiteMap class are shared (static) properties:

  • CurrentNodeEnables you to retrieve the SiteMapNode that corresponds to the current page.

  • EnabledEnables you to determine whether the Site Map is enabled.

  • ProviderEnables you to retrieve the default SiteMapProvider.

  • ProvidersEnables you to retrieve all the configured SiteMapProvders.

  • RootNodeEnables you to retrieve the root SiteMapNode.

The CurrentNode and RootNode properties return a SiteMapNode object. Because a Site Map can contain only one root node, and the root node contains all the other nodes as children, the RootNode property enables you to iterate through all the nodes in a Site Map.

The Provider property returns the default SiteMapProvider. You can use this property to access all the properties and methods of the SiteMapProvider class, such as the FindSiteMapNode() and GetParentNode() methods.

The SiteMap class also supports a single event:

  • SiteMapResolveRaised when the current node is accessed.

You can handle this event to modify the node returned when the current node is retrieved. For example, the Global.asax file in Listing 18.5 automatically adds a new node when the current page does not include a node in the Site Map.

Example 18.5. Global.asax

<%@ Application Language="VB" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">

    Private Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        AddHandler SiteMap.SiteMapResolve, AddressOf SiteMap_SiteMapResolve
    End Sub

    Private Function SiteMap_SiteMapResolve(ByVal sender As Object, ByVal e As SiteMapResolveEventArgs) As SiteMapNode
        If SiteMap.CurrentNode Is Nothing Then
            Dim url As String = e.Context.Request.Path
            Dim title As String = Path.GetFileNameWithoutExtension(url)
            Dim NewNode As New SiteMapNode(e.Provider, url, url, title)
            NewNode.ParentNode = SiteMap.RootNode
            Return NewNode
        End If
        Return SiteMap.CurrentNode
    End Function
</script>

The Application_Start() event handler in Listing 18.5 executes only once when the application first starts. The handler adds a SiteMapResolve event handler to the SiteMap class.

Whenever any control retrieves the current node, the SiteMap_SiteMapResolve() method executes. If there is no node that corresponds to a page, then the method creates a new node and returns it.

The About.aspx page in Listing 18.6 is not included in the Web.sitemap file. However, this page includes a SiteMapPath control. The SiteMapPath control works correctly because the About.aspx page is dynamically added to the Site Map when you access the page (see Figure 18.4).

Adding nodes to a Site Map dynamically.

Figure 18.4. Adding nodes to a Site Map dynamically.

Example 18.6. About.aspx

<%@ Page Language="VB" %>
<!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>About</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:SiteMapPath
        id="SiteMapPath1"
        Runat="server" />

    <hr />

    <h1>About Our Company</h1>

    </div>
    </form>
</body>
</html>

Using the SiteMapNode Class

All pages and folders in a Site Map are represented by instances of the SiteMapNode class. The SiteMapNode class contains the following properties:

  • ChildNodesReturns the child nodes of the current node.

  • DescriptionReturns the description of the current node.

  • HasChildNodesReturns True when the current node has child nodes.

  • ItemReturns a custom attribute (or resource string).

  • KeyReturns a unique identifier for the current node.

  • NextSiblingReturns the next sibling of the current node.

  • ParentNodeReturns the parent node of the current node.

  • PreviousSiblingReturns the previous sibling of the current node.

  • ProviderReturns the SiteMapProvider associated with the current node.

  • ReadOnlyReturns true when a node is read-only.

  • ResourceKeyReturns the resource key associated with the current node (enables localization).

  • RolesReturns the user roles associated with the current node.

  • RootNodeReturns the Site Map root node.

  • TitleReturns the title associated with the current node.

  • UrlReturns the URL associated with the current node.

The SiteMapNode class also supports the following methods:

  • Clone()Returns a clone of the current node.

  • GetAllNodes()Returns all descendent nodes of the current node.

  • GetDataSourceView()Returns a SiteMapDataSourceView object.

  • GetHierarchicalDataSourceView()Returns a SiteMapHierarchicalDataSourceView.

  • IsAccessibleToUser()Returns True when the current user has permissions to view the current node.

  • IsDescendantOf()Returns True when the current node is a descendant of a particular node.

By taking advantage of the SiteMap and SiteMapNode classes, you can work directly with Site Maps in a page. For example, imagine that you want to display the value of the SiteMapNode title attribute in both the browser’s title bar and in the body of the page. Listing 18.7 demonstrates how you can retrieve the value of the Title property associated with the current page programmatically.

Example 18.7. Products/FirstProduct.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/ xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Private Sub Page_Load()
        If Not Page.IsPostBack Then
            Dim currentNode As SiteMapNode = SiteMap.CurrentNode
            Me.Title = currentNode.Title
            ltlBodyTitle.Text = currentNode.Title
            lblDescription.Text = currentNode.Description
        End If
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>First Product</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1><asp:Literal ID="ltlBodyTitle" runat="server" /></h1>

    <asp:Label
        id="lblDescription"
        Runat="server" />

    </div>
    </form>
</body>
</html>

When you open the page in Listing 18.7, the Page_Load() event handler grabs the current SiteMapNode and modifies the Page Title property. The handler also assigns the value of the Title property to a Literal control contained in the body of the page. Finally, the value of the SiteMapNode’s Description property is assigned to a Label control (see Figure 18.5).

Retrieving Site Map node properties.

Figure 18.5. Retrieving Site Map node properties.

Note

It would make sense to place the code in Listing 18.7 in a Master Page. To learn more about Master Pages, see Chapter 5, “Designing Web Sites with Master Pages.”

Advanced Site Map Configuration

This section explores several advanced features of Site Maps. For example, you learn how to display different SiteMap nodes, depending on the roles associated with the current user. You also learn how to create multiple Site Maps for a single application. Finally, you learn how you can extend Site Maps with custom attributes.

Using Security Trimming

You might want to display different navigation links to different users, depending on their roles. For example, if a user is a member of the Administrators role, you might want to display links to pages for administrating the website. However, you might want to hide these links from other users.

To display different links to different users depending on their roles, you must enable a feature of Site Maps named Security Trimming. This feature is disabled by default. The web configuration file in Listing 18.8 enables Security Trimming.

Example 18.8. Web.Config

<?xml version="1.0"?>
<configuration>
  <system.web>

    <authentication mode="Windows" />
    <roleManager enabled="true" />

    <siteMap defaultProvider="MySiteMapProvider">
      <providers>
        <add
          name="MySiteMapProvider"
          type="System.Web.XmlSiteMapProvider"
          securityTrimmingEnabled="true"
          siteMapFile="Web.sitemap" />

      </providers>
    </siteMap>

  </system.web>
</configuration>

Notice that the configuration file in Listing 18.8 includes a <siteMap> element that configures a new SiteMapProvider named MySiteMapProvider. The new provider enables Security Trimming with its securityTrimmingEnabled property.

After you enable Security Trimming, any pages a user is not allowed to view are automatically hidden. For example, imagine that your website includes a folder named Admin that contains the web configuration file in Listing 18.9.

Example 18.9. Web.Config

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>

  <authorization>
    <allow users="WebAdmin" />
    <deny users="*" />
  </authorization>

</system.web>
</configuration>

The configuration file in Listing 18.9 prevents anyone who is not a member of the WebAdmin role from viewing pages in the same folder (and below) as the configuration file. Even if the Web.sitemap file includes nodes that represent pages in the Admin folder, the links don’t appear for anyone except members of the WebAdmin role.

Another option is to explicitly associate roles with nodes in a Site Map. This is useful in two situations. First, if your website contains links to another website, then you can hide or display these links based on the user role. Second, if you explicitly associate roles with pages, then you hide page links even when a user has permission to view a page.

The Web.sitemap file in Listing 18.10 contains links to the Microsoft, Google, and Yahoo! websites. A different set of roles is associated with each link.

Example 18.10. Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode
    title="External Links"
    description="Links to external Websites"
    roles="RoleA,RoleB,RoleC">
    <siteMapNode
      title="Google"
      url="http://www.Google.com"
      description="The Google Website"
      roles="RoleA" />
    <siteMapNode
      title="Microsoft"
      url="http://www.Microsoft.com"
      description="The Microsoft Website"
      roles="RoleB" />
    <siteMapNode
      title="Yahoo"
      url="http://www.Yahoo.com"
      description="The Yahoo Website"
      roles="RoleC" />
  </siteMapNode>
</siteMap>

The page in Listing 18.11 enables you to add yourself and remove yourself from different roles. Notice that different links appear in the TreeView control, depending on which roles you select.

Example 18.11. ShowSecurityTrimming.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/ xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Sub Page_Load()
        If Not Page.IsPostBack Then
            For Each item As ListItem In cblSelectRoles.Items
                If Not Roles.RoleExists(item.Text) Then
                    Roles.CreateRole(item.Text)
                    Roles.AddUserToRole(User.Identity.Name, item.Text)
                End If
            Next
        End If
    End Sub

    Sub btnSelect_Click(ByVal sender As Object, ByVal e As EventArgs)
        For Each item As ListItem In cblSelectRoles.Items
            If item.Selected Then
                If Not User.IsInRole(item.Text) Then
                    Roles.AddUserToRole(User.Identity.Name, item.Text)
                End If
            Else
                If User.IsInRole(item.Text) Then
                    Roles.RemoveUserFromRole(User.Identity.Name, item.Text)
                End If
            End If
        Next
        Response.Redirect(Request.Path)
    End Sub

    Sub Page_PreRender()
        For Each item As ListItem In cblSelectRoles.Items
            item.Selected = User.IsInRole(item.Text)
        Next
    End Sub

</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        html
        {
            background-color:silver;
        }
        .column
        {
            float:left;
            width:300px;
            border:Solid 1px black;
            background-color:white;
            padding:10px;
        }
    </style>
    <title>Show Security Trimming</title>
</head>
<body>
    <form id="form1" runat="server">

    <div class="column">

    <asp:Label
        id="lblSelectRoles"
        Text="Select Roles:"
        AssociatedControlID="cblSelectRoles"
        Runat="server" />

    <br />

    <asp:CheckBoxList
        id="cblSelectRoles"
        Runat="server">
        <asp:ListItem Text="RoleA" />
        <asp:ListItem Text="RoleB" />
        <asp:ListItem Text="RoleC" />
    </asp:CheckBoxList>

    <asp:Button
        id="btnSelect"
        Text="Select"
        OnClick="btnSelect_Click"
        Runat="server" />

    </div>

    <div class="column">

    <asp:TreeView
        id="TreeView1"
        DataSourceID="srcSiteMap"
        Runat="server" />

    <asp:SiteMapDataSource
        id="srcSiteMap"
        Runat="server" />

    </div>
    </form>
</body>
</html>

When you first open the page in Listing 18.11, the Page_Load() handler creates three roles—RoleA, RoleB, and RoleC—and adds the current user to each role.

The CheckBoxList control in the body of the page enables you to select the roles that you want to join. Notice that different links to external websites appear, depending on which roles you select (see Figure 18.6).

Hiding Site Map nodes by user role.

Figure 18.6. Hiding Site Map nodes by user role.

Merging Multiple Site Maps

To make it easier to manage a large application, you can store Site Maps in more than one location and merge the Site Maps at runtime. For example, if you are using the default SiteMapProvider—the XmlSiteMapProvider—then you can create multiple sitemap files that describe the navigation structure of different sections of your website.

For example, the Web.sitemap file in Listing 18.12 includes a node that points to another sitemap file.

Example 18.12. Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode
  url="Default.aspx"
  title="Home"
  description="The Home Page">
  <siteMapNode
    url="Products/Default.aspx"
    title="Our Products"
    description="Products that we offer">
    <siteMapNode
      url="Products/FirstProduct.aspx"
      title="First Product"
      description="The description of the First Product" />
    <siteMapNode
      url="Products/SecondProduct.aspx"
      title="Second Product"
      description="The description of the Second Product" />
  </siteMapNode>
  <siteMapNode
    url="Services"
    title="Our Services"
    description="Services that we offer">
    <siteMapNode
      url="Services/FirstService.aspx"
      title="First Service"
      description="The description of the First Service"
      metaDescription="The first service" />
    <siteMapNode
      url="Services/SecondService.aspx"
      title="Second Service"
      description="The description of the Second Service" />
  </siteMapNode>
  <siteMapNode
    siteMapFile="Employees/Employees.sitemap" />
</siteMapNode>
</siteMap>

The sitemap in Listing 18.12 includes the following node:

<siteMapNode siteMapFile="Employees/Employees.sitemap" />

This node includes a siteMapFile attribute that points to a sitemap located in the Employees subdirectory of the current application. The contents of the Employees.sitemap are automatically merged with the default Web.sitemap.

The Employees.sitemap is contained in Listing 18.13.

Example 18.13. Employees/Employees.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode
    url="Employees/Default.aspx"
    title="Employees"
    description="Contains descriptions of employees">
    <siteMapNode
      url="Employees/BillGates.aspx"
      title="Bill Gates"
      description="Bill Gates Page" />
    <siteMapNode
      url="Employees/SteveJobs.aspx"
      title="Steve Jobs"
      description="Steve Jobs Page" />
  </siteMapNode>
</siteMap>

Notice that there is nothing special about the sitemap in Listing 18.13. It contains a description of the two pages in the Employees subdirectory.

This is a great feature for working with large websites. Each section of the website can be managed by a different developer. When the website is accessed by a user, the contents of the different sitemaps are seamlessly stitched together.

Note

You also can associate different SiteMapProviders with different nodes in a sitemap file by taking advantage of the provider attribute. For example, a Site Map might be stored in a database table for one section of your website and stored in an XML file for another section of your website.

Creating Custom Site Map Attributes

You can extend a Site Map with your own custom attributes. You can use a custom attribute to represent any type of information that you want.

For example, imagine that you want to associate <meta> Description tags with each page in your web application to make it easier for search engines to index your website. In that case, you can add a metaDescription attribute to the nodes in a Web.sitemap file.

The Web.sitemap file in Listing 18.14 includes metaDescription attributes for the two Services pages.

Example 18.14. Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode
    url="Default.aspx"
    title="Home"
    description="The Home Page">
    <siteMapNode
      url="Products/Default.aspx"
      title="Our Products"
      description="Products that we offer">
      <siteMapNode
        url="Products/FirstProduct.aspx"
        title="First Product"
        description="The description of the First Product" />
      <siteMapNode
        url="Products/SecondProduct.aspx"
        title="Second Product"
        description="The description of the Second Product" />
    </siteMapNode>
    <siteMapNode
      url="Services/Default.aspx"
      title="Our Services"
      description="Services that we offer">
      <siteMapNode
        url="Services/FirstService.aspx"
        title="First Service"
        description="The description of the First Service"
        metaDescription="The first service" />
      <siteMapNode
        url="Services/SecondService.aspx"
        title="Second Service"
        description="The description of the Second Service"
        metaDescription="The second service"  />
    </siteMapNode>
  </siteMapNode>
</siteMap>

Visual Web Developer Note

Visual Web Developer displays blue squiggles (warning messages) under any custom attributes in a SiteMap file. You can safely ignore these warnings.

Any custom attributes that you add to a Site Map are exposed by instances of the SiteMapNode class. For example, the page in Listing 18.15 retrieves the value of the metaDescription attribute from the current node and displays the value in an actual <meta> tag.

Example 18.15. Services/FirstService.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/ xhtml11/DTD/xhtml11.dtd">
<script runat="server">

    Private Sub Page_Load()
        Dim meta As HtmlMeta = New HtmlMeta()
        meta.Name = "Description"
        meta.Content = SiteMap.CurrentNode("metaDescription")
        head1.Controls.Add(meta)
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="head1" runat="server">
    <title>First Service</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <h1>The First Service</h1>

    </div>
    </form>
</body>
</html>

After you open the page in Listing 18.15 in a web browser, you can select View, Source to see the <meta> tag added to the source of the page (see Figure 18.7).

Extending a Site Map with a <meta> tag.

Figure 18.7. Extending a Site Map with a <meta> tag.

It is important emphasize that you can do anything you want with custom SiteMapNode attributes. You can represent page titles, section titles, product icons, or anything else with a custom attribute.

Creating Custom Site Map Providers

Site Maps use the provider model. This means that you can easily modify or extend the way Site Maps work by creating your own Site Map provider.

In this section, we create two custom Site Map providers. First, we create the AutoSiteMapProvider. This provider automatically builds a Site Map based on the file and folder structure of a website.

Next, we create a SqlSiteMapProvider. This provider enables you to store a Site Map in a Microsoft SQL Server database table instead of an XML file.

Creating the AutoSiteMapProvider

All Site Map providers inherit from the base SiteMapProvider class. If you want to create your own Site Map provider, then you can override the methods of this base class.

However, in most cases it makes more sense to derive a custom Site Map provider from the base StaticSiteMapProvider class. This is the base class for the default Site Map provider—the XmlSiteMapProvider—and this class includes default implementations of many of the SiteMapProvider methods.

This AutoSiteMapProvider derives from the StaticSiteMapProvider class. It overrides two methods of the base class: GetRootNodeCore() and BuildSiteMap().

The GetRootNodeCore() method returns the root node of the Site Map. The BuildSiteMap() method is the method that is actually responsible for building the Site Map.

The AutoSiteMapProvider is contained in Listing 18.16.

Example 18.16. App_Code/AutoSiteMapProvider.vb

Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Web
Imports System.Web.Caching

Namespace AspNetUnleashed

    Public Class AutoSiteMapProvider
        Inherits StaticSiteMapProvider

        Private _rootNode As SiteMapNode
        Private Shared _excluded As New List(Of String)()
        Private _dependencies As New List(Of String)()

        ''' <summary>
        ''' These folders and pages won't be added
        ''' to the Site Map
        ''' </summary>
        Shared Sub New()
            _excluded.Add("app_code")
            _excluded.Add("app_data")
            _excluded.Add("app_themes")
            _excluded.Add("bin")
        End Sub

        ''' <summary>
        ''' Return the root node of the Site Map
        ''' </summary>
        Protected Overrides Function GetRootNodeCore() As SiteMapNode
            Return BuildSiteMap()
        End Function

        ''' <summary>
        ''' Where all the work of building the Site Map happens
        ''' </summary>
        Public Overrides Function BuildSiteMap() As SiteMapNode
            ' Allow the Site Map to be created by only a single thread
            SyncLock Me
                ' Attempt to get Root Node from Cache
                Dim context As HttpContext = HttpContext.Current
                _rootNode = CType(context.Cache("RootNode"), SiteMapNode)
                If _rootNode Is Nothing Then
                    ' Clear current Site Map
                    Clear()

                    ' Create root node
                    Dim folderUrl As String = HttpRuntime.AppDomainAppVirtualPath
                    Dim defaultUrl As String = folderUrl + "/Default.aspx"
                    _rootNode = New SiteMapNode(Me, folderUrl, defaultUrl, "Home")
                    AddNode(_rootNode)

                    ' Create child nodes
                    AddChildNodes(_rootNode)
                    _dependencies.Add(HttpRuntime.AppDomainAppPath)

                    ' Add root node to cache with file dependencies
                    Dim fileDependency As CacheDependency = New CacheDependency(_dependencies.ToArray())
                    context.Cache.Insert("RootNode", _rootNode, fileDependency)
                End If
                Return _rootNode
            End SyncLock
        End Function

        ''' <summary>
        ''' Add child folders and pages to the Site Map
        ''' </summary>
        Private Sub AddChildNodes(ByVal parentNode As SiteMapNode)
            AddChildFolders(parentNode)
            AddChildPages(parentNode)
        End Sub

        ''' <summary>
        ''' Add child folders to the Site Map
        ''' </summary>
        Private Sub AddChildFolders(ByVal parentNode As SiteMapNode)
            Dim context As HttpContext = HttpContext.Current
            Dim parentFolderPath As String = context.Server.MapPath(parentNode.Key)
            Dim folderInfo As DirectoryInfo = New DirectoryInfo(parentFolderPath)

            ' Get sub folders
            Dim folders() As DirectoryInfo = folderInfo.GetDirectories()
            For Each folder As DirectoryInfo In folders
                If Not _excluded.Contains(folder.Name.ToLower()) Then
                    Dim folderUrl As String = parentNode.Key + "/" + folder.Name
                    Dim folderNode As SiteMapNode = New SiteMapNode(Me, folderUrl, Nothing, GetName(folder.Name))
                    AddNode(folderNode, parentNode)
                    AddChildNodes(folderNode)
                    _dependencies.Add(folder.FullName)
                End If
            Next
        End Sub

        ''' <summary>
        ''' Add child pages to the Site Map
        ''' </summary>
        Private Sub AddChildPages(ByVal parentNode As SiteMapNode)
            Dim context As HttpContext = HttpContext.Current
            Dim parentFolderPath As String = context.Server.MapPath(parentNode.Key)
            Dim folderInfo As DirectoryInfo = New DirectoryInfo(parentFolderPath)

            Dim pages() As FileInfo = folderInfo.GetFiles("*.aspx")
            For Each page As FileInfo In pages
                If Not _excluded.Contains(page.Name.ToLower()) Then
                    Dim pageUrl As String = parentNode.Key + "/" + page.Name
                    If String.Compare(pageUrl, _rootNode.Url, True) <> 0 Then
                        Dim pageNode As SiteMapNode = New SiteMapNode(Me, pageUrl, pageUrl, GetName(page.Name))
                        AddNode(pageNode, parentNode)
                    End If
                End If
            Next
        End Sub

        ''' <summary>
        ''' Fix the name of the page or folder
        ''' by removing the extension and replacing
        ''' underscores with spaces
        ''' </summary>
        Private Function GetName(ByVal name As String) As String
            name = Path.GetFileNameWithoutExtension(name)
            Return Name.Replace("_", " ")
        End Function

    End Class
 End Namespace

Almost all of the work in Listing 18.16 happens in the BuildSiteMap() method. This method recursively iterates through all the folders and pages in the current web application creating SiteMapNodes. When the method completes its work, a Site Map that reflects the folder and page structure of the website is created.

You should notice two special aspects of the code in Listing 18.16. First, file dependencies are created for each folder. If you add a new folder or page to your website, the BuildSiteMap() method is automatically called the next time you request a page.

Second, notice that the constructor for the AutoSiteMapProvider class creates a list of excluded files. For example, this list includes the App_Code and Bin folders. You do not want these files to appear in a Site Map. If there are other special files that you want to hide, then you need to add the filenames to the list of excluded files in the constructor.

After you create the AutoSiteMapProvider class, you need to configure your application to use the custom Site Map provider. You can use the configuration file in Listing 18.17 to enable the AutoSiteMapProvider.

Example 18.17. Web.Config

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.web>

      <siteMap defaultProvider="MyAutoSiteMapProvider">
        <providers>
          <add
            name="MyAutoSiteMapProvider"
            type="AspNetUnleashed.AutoSiteMapProvider" />
        </providers>
      </siteMap>

    </system.web>
</configuration>

The configuration file in Listing 18.17 configures the AutoSiteMapProvider as the application’s default provider.

You can try out the AutoSiteMapProvider by requesting the Default.aspx page from the AutoSiteMapProviderApp Web application contained on the CD that accompanies this book. This application does not include a Web.sitemap file. The Site Map is automatically generated from the structure of the website.

Displaying an automatically generated Site Map.

Figure 18.8. Displaying an automatically generated Site Map.

Creating the SqlSiteMapProvider

For certain applications it makes more sense to store a Site Map in a database table than an XML file. In this section, you can see the creation of the SqlSiteMapProvider, which stores a Site Map in a Microsoft SQL Server database.

To use the SqlSiteMapProvider class, you must create a SQL database table named SiteMap. Furthermore, the SiteMap database table must look like this:

Id

ParentId

Url

Title

Description

1

null

Default.aspx

Home

The Home Page

2

1

 

Products

Products

3

2

Products/FirstProduct.aspx

First Product

The First Product

4

2

Products/SecondProduct.aspx

Second Product

The Second Product

6

1

 

Services

Services

7

6

Services/FirstService.aspx

First Service

The First Service

Each row in the SiteMap table represents a particular Site Map node. The relationship between the nodes is represented by the ParentId column. The row that represents the root node has a ParentId column with the value null. Every other row is either a child of the root node or the child of some other node.

The code for the SqlSiteMapProvider is contained in Listing 18.18.

Example 18.18. App_CodeSqlSiteMapProvider.vb

Imports System
Imports System.Collections.Specialized
Imports System.Web.Configuration
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web
Imports System.Web.Caching

Namespace AspNetUnleashed

    ''' <summary>
    ''' Summary description for SqlSiteMapProvider
    ''' </summary>
    Public Class SqlSiteMapProvider
        Inherits StaticSiteMapProvider

        Private _isInitialized As Boolean = False
        Private _connectionString As String
        Private _rootNode As SiteMapNode

        ''' <summary>
        ''' Initialize provider with database
        ''' connection string
        ''' </summary>
        Public Overrides Sub Initialize(ByVal name As String, ByVal attributes As NameValueCollection)
            If _isInitialized Then
                Return
            End If

            MyBase.Initialize(name, attributes)

            Dim connectionStringName As String = attributes("connectionStringName")
            If String.IsNullOrEmpty(connectionStringName) Then
                Throw New Exception("You must provide a connectionStringName attribute")
            End If

            _connectionString = WebConfigurationManager.ConnectionStrings(connectionStringName). ConnectionString
            If String.IsNullOrEmpty(_connectionString) Then
                Throw New Exception("Could not find connection String " &
[ic;ccc] connectionStringName)
            End If

            _isInitialized = True
        End Sub

        ''' <summary>
        ''' Return root node by calling
        ''' BuildSiteMap
        ''' </summary>
        Protected Overrides Function GetRootNodeCore() As SiteMapNode
            Return BuildSiteMap()
        End Function

        ''' <summary>
        ''' Build the Site Map and
        ''' create SQL Cache Dependency
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function BuildSiteMap() As SiteMapNode
            ' Only allow the Site Map to be created by a single thread
            SyncLock Me
                ' Attempt to get Root Node from Cache
                Dim context As HttpContext = HttpContext.Current
                _rootNode = CType(context.Cache("RootNode"), SiteMapNode)

                If _rootNode Is Nothing Then
                    HttpContext.Current.Trace.Warn("Loading from database")

                    ' Clear current Site Map
                    Clear()

                    ' Load the database data
                    Dim tblSiteMap As DataTable = GetSiteMapFromDB()

                    ' Get the root node
                    _rootNode = GetRootNode(tblSiteMap)
                    AddNode(_rootNode)

                    ' Build the child nodes
                    BuildSiteMapRecurse(tblSiteMap, _rootNode)

                    ' Add root node to cache with database dependency
                    Dim sqlDepend As SqlCacheDependency = New SqlCacheDependency("SiteMapDB", "SiteMap")
                    context.Cache.Insert("RootNode", _rootNode, sqlDepend)
                End If
                Return _rootNode
            End SyncLock
        End Function

        ''' <summary>
        ''' Loads Site Map from Database
        ''' </summary>
        Private Function GetSiteMapFromDB() As DataTable
            Dim selectCommand As String = "SELECT Id,ParentId,Url,Title,Description FROM SiteMap"
            Dim dad As New SqlDataAdapter(selectCommand, _connectionString)
            Dim tblSiteMap As New DataTable()
            dad.Fill(tblSiteMap)
            Return tblSiteMap
        End Function

        ''' <summary>
        ''' Gets the root node by returning row
        ''' with null ParentId
        ''' </summary>
        Private Function GetRootNode(ByVal siteMapTable As DataTable) As SiteMapNode
            Dim results() As DataRow = siteMapTable.Select("ParentId IS NULL")
            If results.Length = 0 Then
                Throw New Exception("No root node in database")
            End If
            Dim rootRow As DataRow = results(0)
            Return New SiteMapNode(Me, rootRow("Id").ToString(), rootRow("url").ToString(), rootRow("title").ToString(), rootRow("description").ToString())
        End Function

        ''' <summary>
        ''' Recursively builds a Site Map by iterating ParentId
        ''' </summary>
        Private Sub BuildSiteMapRecurse(ByVal siteMapTable As DataTable, ByVal parentNode As SiteMapNode)
            Dim results() As DataRow = siteMapTable.Select("ParentId=" + parentNode.Key)
            For Each row As DataRow In results
                Dim node As SiteMapNode = New SiteMapNode(Me, row("Id").ToString(),  row("url").ToString(), row("title").ToString(), row("description").ToString())
                AddNode(node, parentNode)
                BuildSiteMapRecurse(siteMapTable, node)
            Next
        End Sub

    End Class
 End Namespace

Like the custom Site Map provider that was created in the previous section, the SqlSiteMapProvider derives from the base StaticSiteMapProvider class. The SqlSiteMapProvider class overrides three methods of the base class: Initialize(), GetRootNodeCore(), and BuildSiteMap().

The Initialize() method retrieves a database connection string from the web configuration file. If a database connection string cannot be retrieved, then the method throws a big, fat exception.

Almost all the work happens in the BuildSiteMap() method. This method loads the contents of the SiteMap database table into an ADO.NET DataTable. Next, it recursively builds the Site Map nodes from the DataTable.

There is one special aspect of the code in Listing 18.18. It uses a SQL cache dependency to automatically rebuild the Site Map when the contents of the SiteMap database table are changed.

To enable SQL cache dependencies for a database, you must configure the database with either the enableNotifications tool or the aspnet_regsql tool. Use the enableNotifications tool when enabling SQL cache dependencies for a SQL Express database table, and use the aspnet_regsql tool when enabling SQL cache dependencies for the full version of Microsoft SQL Server.

Note

To learn more about configuring SQL cache dependencies, see Chapter 23, “Caching Application Pages and Data.”

To enable SQL cache dependencies for a SQL Express database named SiteMapDB that contains a table named SiteMap, browse to the folder that contains the SiteMapDB.mdf file and execute the following command from a Command Prompt:

enableNotifications "SiteMapDB.mdf" "SiteMap"

You can configure your website to use the SqlSiteMapProvider class with the Web configuration file in Listing 18.19.

Example 18.19. Web.Config

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add
      name="conSiteMap"
      connectionString="Data Source=.SQLExpress;Integrated
 Security=True;AttachDbFileName=|DataDirectory|SiteMapDB.mdf;User Instance=True"/>
  </connectionStrings>

    <system.web>
      <siteMap defaultProvider="myProvider">
        <providers>
          <add
            name="myProvider"
            type="AspNetUnleashed.SqlSiteMapProvider"
            connectionStringName="conSiteMap" />

        </providers>
      </siteMap>

        <caching>
        <sqlCacheDependency enabled = "true" pollTime = "5000" >
          <databases>
            <add name="SiteMapDB"
                 connectionStringName="conSiteMap"
            />
          </databases>
        </sqlCacheDependency>
        </caching>

      </system.web>
</configuration>

The configuration file in Listing 18.19 accomplishes several tasks. First, it configures the SqlSiteMapProvider as the default Site Map provider. Notice that the provider includes a connectionStringName attribute that points to the connection string for the local SQL Express database named SiteMapDB.

The configuration file also enables SQL cache dependency polling. The application is configured to poll the SiteMapDB database for changes every five seconds. In other words, if you make a change to the SiteMap database table, the Site Map is updated to reflect the change within five seconds.

You can try out the SqlSiteMapProvider by opening the Default.aspx page included in the SqlSiteMapProviderApp web application on the CD that accompanies this book. If you modify the SiteMap database table, the changes are automatically reflected in the Site Map (see Figure 18.9).

Displaying a Site Map from a Microsoft SQL database.

Figure 18.9. Displaying a Site Map from a Microsoft SQL database.

Generating a Google SiteMap File

Google provides a free service, named Google SiteMaps, that you can use to monitor and improve the way that Google indexes the pages on your website. For example, you can use Google SiteMaps to discover which Google search queries have returned pages from your website and the ranking of your pages in Google search results. You also can use Google SiteMaps to view any problems that the Google crawler encounters when indexing your site.

You can sign up for Google SiteMaps by visiting the following URL:

http://www.google.com/webmasters/sitemaps

To use Google SiteMaps, you must provide Google with the URL of a Google SiteMap file hosted on your website. The Google SiteMap file is an XML file that contains a list of URLs you want Google to index.

The Google SiteMap XML file has the following format:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
  <url>
    <loc>http://www.example.com/</loc>
    <lastmod>2005-01-01</lastmod>
  </url>
  <url>
    <loc>http://www.example.com/sample.html/</loc>
    <lastmod>2006-03-11</lastmod>
  </url>
</urlset>

The Google SiteMap file contains a simple list of <url> elements that contain <loc> elements representing the location of the URL and <lastmod> elements representing the last modified date of the URL.

Note

The Google SiteMap file also can contain <changefreq> and <priority> elements. The <changefreq> element indicates how frequently a URL changes, and the <priority> element represents the priority of a URL relative to other URLs in your site. These elements are optional and are ignored here.

You can generate a Google SiteMap file automatically from an ASP.NET SiteMap. The HTTP Handler in Listing 18.20 generates a Google SiteMap that conforms to Google’s requirements for a valid SiteMap file.

Example 18.20. PublicSiteMap.ashx

<%@ WebHandler Language="VB" Class="PublicSiteMap" %>
Imports System
Imports System.Web
Imports System.Xml
Imports System.Text
Imports System.IO

Public Class PublicSiteMap
    Implements IHttpHandler

    Private _xmlWriter As XmlWriter

    Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        context.Response.ContentType = "text/xml"

        Dim settings As New XmlWriterSettings()
        settings.Encoding = Encoding.UTF8
        settings.Indent = True
        _xmlWriter = XmlWriter.Create(context.Response.OutputStream, settings)
        _xmlWriter.WriteStartDocument()
        _xmlWriter.WriteStartElement("urlset", "http://www.google.com/schemas/sitemap/0.84")

        ' Add root node
        AddUrl(SiteMap.RootNode)

        ' Add all other nodes
        Dim nodes As SiteMapNodeCollection = SiteMap.RootNode.GetAllNodes()
        For Each node As SiteMapNode In nodes
            AddUrl(node)
        Next

        _xmlWriter.WriteEndElement()
        _xmlWriter.WriteEndDocument()
        _xmlWriter.Flush()
    End Sub

    Private  Sub AddUrl(ByVal node As SiteMapNode)
        ' Skip empty Urls
        If String.IsNullOrEmpty(node.Url) Then
            Return
        End If
        ' Skip remote nodes
        If node.Url.StartsWith("http",True,Nothing) Then
            Return
        End If
        ' Open url tag
        _xmlWriter.WriteStartElement("url")
        ' Write location
        _xmlWriter.WriteStartElement("loc")
        _xmlWriter.WriteString(GetFullUrl(node.Url))
        _xmlWriter.WriteEndElement()
        ' Write last modified
        _xmlWriter.WriteStartElement("lastmod")
        _xmlWriter.WriteString(GetLastModified(node.Url))
        _xmlWriter.WriteEndElement()
        ' Close url tag
        _xmlWriter.WriteEndElement()
    End Sub

    Private Function GetFullUrl(ByVal url As String) As String
        Dim context As HttpContext =  HttpContext.Current
        Dim server As String =  context.Request.Url.GetComponents( UriComponents.SchemeAndServer,UriFormat.UriEscaped)
        Return Combine(server,url)
    End Function

    Private Function Combine(ByVal baseUrl As String, ByVal url As String) As String
        baseUrl = baseUrl.TrimEnd(New Char() {"/"c})
        url = url.TrimStart(New Char() {"/"c})
        Return baseUrl + "/" + url
    End Function

    Private Function GetLastModified(ByVal url As String) As String
        Dim context As HttpContext =  HttpContext.Current
        Dim physicalPath As String =  context.Server.MapPath(url)
        Return File.GetLastWriteTimeUtc(physicalPath).ToString("s")
    End Function

    Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return True
        End Get
    End Property
 End Class

The HTTP Handler in Listing 18.20 generates an XML file by iterating through each of the nodes in an ASP.NET Site Map. The XML file is created with the help of the XmlWriter class. This class is used to generate each of the XML tags.

Note

You can think of an HTTP Handler is a lightweight ASP.NET page. You learn about HTTP Handlers in Chapter 25, “Working with the HTTP Runtime.”

The file in Listing 18.21 contains the XML file returned by the PublicSiteMap.ashx handler when the Handler is called from the sample application contained on the CD that accompanies this book. (The file has been abridged for reasons of space.)

Example 18.21. PublicSiteMap.ashx Results

<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
  <url>
    <loc>http://localhost:2905/SiteMaps/Default.aspx</loc>
    <lastmod>2005-10-30T03:13:58</lastmod>
  </url>
  <url>
    <loc>http://localhost:2905/SiteMaps/Products/Default.aspx</loc>
    <lastmod>2005-10-28T21:48:04</lastmod>
  </url>
  <url>
    <loc>http://localhost:2905/SiteMaps/Services</loc>
    <lastmod>2005-10-30T04:31:57</lastmod>
  </url>
  <url>
    <loc>http://localhost:2905/SiteMaps/Employees/Default.aspx</loc>
    <lastmod>1601-01-01T00:00:00</lastmod>
  </url>
  <url>
    <loc>http://localhost:2905/SiteMaps/Products/FirstProduct.aspx</loc>
    <lastmod>2005-10-30T03:43:52</lastmod>
  </url>
</urlset>

When you sign up at the Google SiteMaps website, submit the URL of the PublicSiteMap.ashx file when you are asked to enter your SiteMap URL. The Google service retrieves your SiteMap from the handler automatically.

Summary

In this chapter, you learned how to work with Site Maps. The first section discussed the SiteMapDataSource control. You learned how to declaratively represent different sets of nodes in a Site Map with this control.

Next, the SiteMap and SiteMapNode classes were examined. You learned how to create new Site Map nodes dynamically by handling the SiteMapResolve event. You also learned how to programmatically retrieve the current Site Map node in a page.

The next section discussed several advanced features of Site Maps. You learned how to display different Site Map nodes to different users depending on their roles. You also learned how to merge SiteMap files located in different subfolders. Finally, you learned how to extend Site Maps with custom attributes.

We also built two custom Site Map providers. We created an AutoSiteMapProvider that automatically builds a Site Map that reflects the folder and page structure of a website. We also created a SqlSiteMapProvider that stores a Site Map in a Microsoft SQL Server database table.

Finally, you learned how to use ASP.NET Site Maps with Google SiteMaps. In the final section of this chapter, you learned how to create a custom HTTP Handler that converts an ASP.NET Site Map into a Google SiteMap so that you can improve the way that Google indexes your website’s pages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset