Wednesday, August 6, 2008

Advantage SiteMap Provider

This post is one of a series of posts about the Advantage Providers for ASP.NET other posts in this series are listed below.

A SiteMap Provider is another part of the ASP.NET Provider Model and is used to provide information about the structure of the web site. This provider is generally used with a SiteMapDataSoruce control which provides information to various visual controls such as the SiteMapPath which provides a breadcrumb path. The SiteMapDataSource can also be consumed by any other data aware control.

As with the other Advantage Providers the Advantage SiteMap provider uses an Advantage table to store site information. Unlike the other providers the SiteMap provider only reads the data from the table there are no methods for manipulating the data through the provider. You must use some other mechanism for adding pages to the site map.

The Advantage SiteMap provider builds a SiteMapNode by reading the information from the SiteMap table in the data dictionary. Once this SiteMapNode is populated it can be used by the SiteMapDataSource. The node is populated using the BuildSiteMap public method and some helper functions. The BuildSiteMap method reads the data from the SiteMap table into a DataReader which is then used to create the SiteMapNode.

   1: public override SiteMapNode BuildSiteMap()
   2: {
   3:     lock (this)
   4:     {
   5:         // Return immediately if this method has been called before
   6:         if (_root != null)
   7:             return _root;
   8:  
   9:         // Query the database for site map nodes
  10:         AdsConnection connection = new AdsConnection(_connect);
  11:  
  12:         try
  13:         {
  14:             connection.Open();
  15:             AdsCommand command = new AdsCommand("SELECT * FROM SiteMap", connection);
  16:             AdsDataReader reader = command.ExecuteReader();
  17:             _indexID = reader.GetOrdinal("ID");
  18:             _indexUrl = reader.GetOrdinal("Url");
  19:             _indexTitle = reader.GetOrdinal("Title");
  20:             _indexDesc = reader.GetOrdinal("Description");
  21:             _indexRoles = reader.GetOrdinal("Roles");
  22:             _indexParent = reader.GetOrdinal("Parent");
  23:  
  24:             if (reader.Read())
  25:             {
  26:                 // Create the root SiteMapNode and add it to the site map
  27:                 _root = CreateSiteMapNodeFromDataReader(reader);
  28:                 AddNode(_root, null);
  29:  
  30:                 // Build a tree of SiteMapNodes underneath the root node
  31:                 while (reader.Read())
  32:                 {
  33:                     // Create another site map node and add it to the site map
  34:                     SiteMapNode node = CreateSiteMapNodeFromDataReader(reader);
  35:                     AddNode(node, GetParentNodeFromDataReader(reader));
  36:                 }
  37:             }
  38:         }
  39:         finally
  40:         {
  41:             connection.Close();
  42:         }
  43:  
  44:         // Return the root SiteMapNode
  45:         return _root;
  46:     }
  47: }

A Lock is used making this method a critical section for thread safety. If the SiteMapNode has already been populated then it is returned to the calling method. Site maps do not generally change very often so reloading the node each time should be unnecessary. Once the table has been read the indexes of the various SiteMap items are determined and the root node is created using the CreateSiteMapNodeFromDataReader method.

   1: private SiteMapNode CreateSiteMapNodeFromDataReader(AdsDataReader reader)
   2: {
   3:     // Make sure the node ID is present
   4:     if (reader.IsDBNull(_indexID))
   5:         throw new ProviderException(_errmsg1);
   6:  
   7:     // Get the node ID from the DataReader
   8:     int id = reader.GetInt32(_indexID);
   9:  
  10:     // Make sure the node ID is unique
  11:     if (_nodes.ContainsKey(id))
  12:         throw new ProviderException(_errmsg2);
  13:  
  14:     // Get title, URL, description, and roles from the DataReader
  15:     string title = reader.IsDBNull(_indexTitle) ? null : reader.GetString(_indexTitle).Trim();
  16:     string url = reader.IsDBNull(_indexUrl) ? null : reader.GetString(_indexUrl).Trim();
  17:     string description = reader.IsDBNull(_indexDesc) ? null : reader.GetString(_indexDesc).Trim();
  18:     string roles = reader.IsDBNull(_indexRoles) ? null : reader.GetString(_indexRoles).Trim();
  19:  
  20:     // If roles were specified, turn the list into a string array
  21:     string[] rolelist = null;
  22:     if (!String.IsNullOrEmpty(roles))
  23:         rolelist = roles.Split(new char[] { ',', ';' }, 512);
  24:  
  25:     // Create a SiteMapNode
  26:     SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description, 
  27:                                        rolelist, null, null, null);
  28:  
  29:     // Record the node in the _nodes dictionary
  30:     _nodes.Add(id, node);
  31:  
  32:     // Return the node        
  33:     return node;
  34: }

Notice that a SiteMap node has a roles property which can be used to restrict what the user sees. The SiteMap can be trimmed so only the pages the user can access are displayed. After the first node has been created subnodes are added based on their parent nodes. The parent is specified in the SiteMap table in the Parent field and must be specified for all but the first record in the table. The Parent field contains the ID of the node under which the subnode will fall.

You can download the provider from CodeCentral on the DevZone under the WebApps category. You can also use this direct link to the Advantage ASP.NET Providers project. I'll be discussing the implementation of these providers next.

No comments: