Site Map Providers

 

Microsoft ASP.NET 2.0 Providers: Introduction
Membership Providers
Role Providers
Site Map Providers
Session State Providers
Profile Providers
Web Event Providers
Web Parts Personalization Providers

Introduction

Site map providers provide the interface between Microsoft ASP.NET's data-driven site-navigation features and site map data sources. ASP.NET 2.0 ships with one site map provider: XmlSiteMapProvider, which reads site maps from XML site map files.

The fundamental job of a site map provider is to read site map data from a data source and build an upside-down tree of SiteMapNode objects (see Figure 4-1), and to provide methods for retrieving nodes from the site map. Each SiteMapNode in the tree represents one node in the site map. SiteMapNode properties such as Title, Url, ParentNode, and ChildNodes define the characteristics of each node, and allow the tree to be navigated up, down, and sideways. A single site map can be managed by one or several providers. Site map providers can form a tree of their own, linked together by their ParentProvider properties, with each provider in the tree claiming responsibility for a subset of the site map. A SiteMapNode's Provider property identifies the provider that "owns" that node.

Aa478951.asp2prvdr0401(en-us,MSDN.10).gif

Figure 4-1. Site map structure

The Microsoft .NET Framework's System.Web namespace includes a class named SiteMapProvider that defines the basic characteristics of a site map provider. It also contains a SiteMapProvider-derivative named StaticSiteMapProvider that provides default implementations of most of SiteMapProvider's abstract methods, and that overrides key virtuals to provide functional, even optimized, implementations. Providers that derive from StaticSiteMapProvider require considerably less code than providers derived from SiteMapProvider. StaticSiteMapProvider is prototyped as follows:

public abstract class StaticSiteMapProvider : SiteMapProvider
{
    public abstract SiteMapNode BuildSiteMap();
    protected virtual void Clear() {}
    protected internal override void AddNode(SiteMapNode node,
        SiteMapNode parentNode) {}
    protected internal override void RemoveNode(SiteMapNode node) {}
    public override SiteMapNode FindSiteMapNode(string rawUrl) {}
    public override SiteMapNode FindSiteMapNodeFromKey(string key) {}
    public override SiteMapNodeCollection
        GetChildNodes(SiteMapNode node) {}
    public override SiteMapNode GetParentNode(SiteMapNode node) {}
}

One of the key features of a site map provider is security trimming, which restricts the visibility of site map nodes, based on users' role memberships. The SiteMapProvider class contains built-in support for security trimming. SiteMapProvider implements a Boolean read-only property named SecurityTrimmingEnabled, which indicates whether security trimming is enabled. Furthermore, SiteMapProvider's Initialize method initializes this property from the provider's securityTrimmingEnabled configuration attribute. Internally, SiteMapProvider methods that retrieve nodes from the site map call the provider's virtual IsAccessibleToUser method to determine whether nodes can be retrieved. All a derived class has to do to support security trimming is initialize each SiteMapNode's Roles property with an array of role names identifying users that are permitted to access that node, or with * if everyone (including unauthenticated users and users who enjoy no role memberships) is permitted.

Note The default implementation of SiteMapProvider.IsAccessibleToUser makes use of the URL and file authorization checks built into ASP.NET. Consequently, a site map provider with security trimming enabled frequently needs only * in the roles attribute for the site map's root <siteMapNode>. Explicitly specifying the roles attribute for other nodes is necessary only for nodes that don't have a URL (or whose URL targets a destination outside the application's directory hierarchy), and for nodes whose visibility you wish to expand beyond the boundaries that URL and file authorization would normally allow.

The following section documents the implementation of XmlSiteMapProvider, which derives from StaticSiteMapProvider.

XmlSiteMapProvider

XmlSiteMapProvider is the Microsoft XML site map provider. It reads site map data from XML files that utilize the schema documented in "Data Schema4." It includes localization support that enables localized node titles and descriptions to be loaded from resources (see "Localization"), and it supports security trimming. (Other than flowing its own SecurityTrimmingEnabled property down to child instances of XmlSiteMapProvider, and including logic to parse comma-delimited or semicolon-delimited lists of role names into string arrays, XmlSiteMapProvider doesn't do anything special to support security trimming; that support comes by inheritance from StaticSiteMapProvider ** and SiteMapProvider.) It also employs a mechanism for automatically refreshing the site map if the site map file changes (see "Refreshing the Site Map").

The ultimate reference for XmlSiteMapProvider is the XmlSiteMapProvider source code, which is found in XmlSiteMapProvider.cs. The sections that follow highlight key aspects of XmlSiteMapProvider's design and operation.

Provider Initialization

Initialization occurs in XmlSiteMapProvider.Initialize, which is called one time—when the provider is loaded—by ASP.NET. XmlSiteMapProvider.Initialize processes the siteMapFile configuration attribute (if present), and converts it into an object representing the virtual path to the site map file. Then, after deferring to the base class's Initialize method to process other attributes such as securityTrimmingEnabled, XmlSiteMapProvider.Initialize throws a ProviderException if unrecognized configuration attributes remain.

Data Schema

XmlSiteMapProvider reads site map data from XML files structured like the one in Listing 4-1. Each <siteMapNode> element defines one node in the site map, and can include the following attributes:

  • title, which specifies the text displayed for the node in a navigation UI
  • description, which provides descriptive text that may be shown in a navigation UI (for example, when the cursor hovers a node)
  • url, which identifies the target of the link
  • roles, which specifies which roles the node is visible to in a navigation UI when security trimming is enabled
  • resourceKey, which specifies a resource key used to load localized text from localization resources

Site map files must contain a root element named <siteMap>, and the <siteMap> element can contain only one root <siteMapNode>.

Listing 4-1. Sample site map file

<siteMap>
  <siteMapNode title="Home" description="Home" url="~/default.aspx">
    <siteMapNode title="Products" description="Our products"
      url="~/Products.aspx" roles="*">
      <siteMapNode title="Hardware" description="Hardware choices"
        url="~/Hardware.aspx" />
      <siteMapNode title="Software" description="Software choices"
        url="~/Software.aspx" />
    </siteMapNode>
    <siteMapNode title="Services" description="Services we offer"
        url="~/Services.aspx" roles="*">
        <siteMapNode title="Training" description="Training classes"
          url="~/Training.aspx" />
        <siteMapNode title="Consulting"
          description="Consulting services" 
          url="~/Consulting.aspx" />
        <siteMapNode title="Support" description="Supports plans" 
          url="~/Support.aspx" />
    </siteMapNode>
    <siteMapNode title="Members Only" description="Premium content"
        url="~/Members.aspx" roles="Members,Administrators">
        <siteMapNode title="Account Management"
          description="Manage your account"
          url="~/MembersOnly/Accounts.aspx" />
        <siteMapNode title="Discussion Forums"
          description="Converse with other members"
          url="~/MembersOnly/Forums.aspx" />
    </siteMapNode>
  </siteMapNode>
</siteMap>

A <siteMapNode> element may also include a provider attribute that delegates responsibility for that node and its children to another provider. The provider attribute is not valid if the <siteMapNode> element contains other attributes. In addition, a <siteMapNode> element can contain a siteMapFile attribute pointing to another site map file. That attribute, too, is valid only in the absence of other attributes.

XmlSiteMapProvider doesn't validate XML site map files against an XML schema. Instead, the schema is implicit in XmlSiteMapProvider's reading and handling of site map data. For example, the following code checks for a root <siteMap> element, and throws an exception if the element doesn't exist:

XmlNode node = null;
foreach (XmlNode siteMapNode in document.ChildNodes) {
    if (String.Equals(siteMapNode.Name, "siteMap",
        StringComparison.Ordinal)) {
        node = siteMapNode;
        break;
    }
}

if (node == null)
    throw new ConfigurationErrorsException(SR.GetString(
        SR.XmlSiteMapProvider_Top_Element_Must_Be_SiteMap), document);

XmlSiteMapProvider uses an XmlTextReader to read the site map file, and then wraps the nodes in an XmlDocument for easy navigation as it builds the site map in memory.

Building the Site Map

The heart of XmlSiteMapProvider is its BuildSiteMap method, which is called by ASP.NET when it needs the site map from the provider. Because BuildSiteMap is called many times over the provider's lifetime, XmlSiteMapProvider.BuildSiteMap contains logic for building the site map from the XML site map file the first time BuildSiteMap is called, and for returning the existing site map in subsequent calls.

XmlSiteMapProvider.BuildSiteMap reads the site map from the file specified through the siteMapFile configuration attribute, and builds an in-memory tree of SiteMapNode objects. Before building the site map, BuildSiteMap performs a series of validation checks on the site map data, making sure, for example, that it contains a root <siteMap> element, and that <siteMap> contains one (and only one) <siteMapNode> element. BuildSiteMap also checks the <siteMap> element for an enableLocalization attribute and, if present, initializes its own EnableLocalization property (inherited from SiteMapProvider) accordingly.

To convert XML nodes into SiteMapNodes, and to build the SiteMapNode tree, BuildSiteMap creates an instance of System.Collections.Queue, adds the root XML <siteMapNode> to the queue, and calls a method named ConvertFromXmlNode. With help from helper methods such as GetNodeFromProvider, GetNodeFromSiteMapFile, GetNodeFromXmlNode, and AddNodeInternal, ConvertFromXmlNode walks the XML site map from top to bottom, converting XML nodes into SiteMapNodes, and adding them to the tree. The SiteMapNode reference returned by ConvertFromXmlNode represents the SiteMapNode at the top of the tree (the root SiteMapNode). That reference is returned by BuildSiteMap to hand the site map off to ASP.NET. The GetNodeFromProvider and GetNodeFromSiteMapFile methods enable XmlSiteMapProvider to link one site map to other site maps managed by separate instances of SiteMapProviders.

As it converts XML nodes into SiteMapNodes, XmlSiteMapProvider verifies that each SiteMapNode's Url and Key properties are unique with respect to other SiteMapNodes. For nodes that have URLs assigned to them, XmlSiteMapProvider sets Key equal to Url. For nodes that do not have URLs assigned to them, XmlSiteMapProvider sets Key to a randomly generated GUID. XmlSiteMapProvider also verifies that node URLs are application-relative, and that they don't contain URL-encoded characters—a sign of malformed (and potentially dangerous) URLs.

Refreshing the Site Map

Early in its lifetime, BuildSiteMap calls a helper method named GetConfigDocument that registers an event handler named OnConfigFileChange to be called if, and when, the site map file changes:

_handler = new FileChangeEventHandler(this.OnConfigFileChange);
HttpRuntime.FileChangesMonitor.StartMonitoringFile
    (_filename, _handler);

If the site map file changes, OnConfigFileChange notifies the parent provider (if any), and then calls the Clear method inherited from StaticSiteMapProvider to clear the site map, as follows:

private void OnConfigFileChange(Object sender, FileChangeEvent e) {
    // Notifiy the parent for the change.
    XmlSiteMapProvider parentProvider =
        ParentProvider as XmlSiteMapProvider;
    if (parentProvider != null) {
        parentProvider.OnConfigFileChange(sender, e);
    }
    Clear();
}

The next time ASP.NET calls BuildSiteMap, XmlSiteMapProvider rebuilds the site map. Consequently, changing the site map file at run-time causes XmlSiteMapProvider to refresh the site map. The change is visible in TreeViews, Menus, or other controls that render navigation UIs from the site map.

XmlSiteMapProvider cancels the file-change monitor at dispose time, as follows:

protected virtual void Dispose(bool disposing) {
    if (_handler != null) {
        Debug.Assert(_filename != null);
        HttpRuntime.FileChangesMonitor.StopMonitoringFile
            (_filename, _handler);
    }
}

This simple bit of housekeeping prevents an active file-change monitor from referencing an event handler that no longer exists.

Localization

As an aid in localizing navigation UIs for users around the world, XmlSiteMapProvider offers built-in support for loading node titles and descriptions from string resources. For an excellent overview of how to localize site maps, see "How to Localize Site Map Data."

XmlSiteMapProvider supports two types of localization expressions: implicit and explicit. Implicit expressions use resourceKey attributes to specify the root names of localization resources. The provider is responsible for parsing the localization expressions, whereas SiteMapNode performs the actual resource lookups at runtime (more on this in a moment). Root names are combined with attribute names to generate fully qualified resource names. The following node definition loads values for title and description from string resources named HomePage.title and HomePage.description in RESX files named Web.sitemap.culture.resx, where culture is a culture identifier such as fr or en-us:

<siteMapNode resourceKey="HomePage" title="Home"
  description="My home page" url="~/Default.aspx">

The default title and description are used if the resource manager can't find a RESX with the appropriate culture—for example, if the requested culture is fr, but the application contains RESX files only for de and us. The current culture—the one that determines which RESX resources are loaded from—is determined by the CurrentThread.CurrentUICulture property. The value of that property can be set programmatically; or, it can be declaratively initialized to the culture specified in each HTTP request's Accept-Language header, by setting UICulture to auto in an @ Page directive, or by setting uiCulture to auto in a <globalization> configuration element.

Explicit expressions don't rely on resourceKey attributes; instead, they use explicit resource names. In the following example, the node title comes from the resource named HomePageTitle in NavResources.culture.resx, whereas the node description comes from the resource named HomePageDesc (also in NavResources.culture.resx):

<siteMapNode title="$resources:NavResources,HomePageTitle,Home"
  description="$resources:NavResources,HomePageDesc,My home page"
  url="~/Default.aspx">

Once more, the current culture is defined by the value of CurrentThread.CurrentUICulture.

SiteMapNode performs the bulk of the work in loading node titles and descriptions from resources. All XmlSiteMapProvider has to do is parse out the resource keys—explicit and implicit—and pass them to SiteMapNode's constructor, as follows:

node = new SiteMapNode(this, key, url, title, description, roleList,
    attributeCollection, resourceKeyCollection, resourceKey);

For a node that uses an implicit expression, XmlSiteMapProvider passes the resource key in resourceKey. For a node that uses explicit expressions, XmlSiteMapProvider passes a collection of resource keys in resourceKeyCollection. Each item in the collection is either an attribute name/class name pair (for example, title and NavResources) or an attribute name/key name pair (for example, title and HomePageTitle). The parsing of explicit resource keys is handled by the private XmlSiteMapProvider.HandleResourceAttribute method, which is called by XmlSiteMapProvider.GetNodeFromXmlNode.

After the resource keys are provided to SiteMapNode's constructor, SiteMapNode handles the task of loading the resources. The key code is contained in the get accessors for SiteMapNode's Title and Description properties, which include logic for loading property values from string resources when implicit or explicit resource keys are provided.

Differences Between the Published Source Code and the .NET Framework's XmlSiteMapProvider

The published source code for XmlSiteMapProvider and StaticSiteMapProvider differs from the .NET Framework versions in the following respects:

  • Declarative and imperative CAS demands were commented out. Because the source code can be compiled standalone, and thus will run as user code rather than trusted code in the global assembly cache, the CAS demands are not strictly necessary. For reference, however, the original demands from the .NET Framework version of the provider have been retained as comments.
  • The automatic reloading of site map data based on file-change monitoring was commented out, because the implementation depends on some internal unmanaged code helpers.

The standalone version of XmlSiteMapProvider supports linking only to child providers that are instances of the standalone version of XmlSiteMapProvider. The version of the provider that ships in the .NET Framework can be linked to child providers of any arbitrary type that implements SiteMapProvider.

Return to part 3, Role Providers.

Go on to part 5, Session State Providers.

© Microsoft Corporation. All rights reserved.