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 providerthe XmlSiteMapProviderand 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.

Listing 18.16. App_Code/AutoSiteMapProvider.vb

[View full width]

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.

Listing 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.

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.

Listing 18.18. App_Code\SqlSiteMapProvider.vb

[View full width]

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").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.

Listing 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).

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





ASP. NET 2.0 Unleashed
ASP.NET 2.0 Unleashed
ISBN: 0672328232
EAN: 2147483647
Year: 2006
Pages: 276

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net