Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

568 C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

Defining a Site Map

The starting point in site map–based navigation is the site map provider. ASP.NET ships with a single site map provider, named XmlSiteMapProvider, which is able to retrieve site map information from an XML file. If you want to retrieve a site map from another location or in a custom format, you’ll need to create your own site map provider—a topic covered in the section “Creating a Custom SiteMapProvider.”

The XmlSiteMapProvider looks for a file named Web.sitemap in the root of the virtual directory. Like all site map providers, its task is to extract the site map data and create the corresponding SiteMap object. This SiteMap object is then made available to other controls through the SiteMapDataSource.

To try this, you need to begin by creating a Web.sitemap file and defining the website structure using the <siteMap> and <siteMapNode> elements.

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <siteMapNode>

<siteMapNode>...</siteMapNode> <siteMapNode>...</siteMapNode>

...

</siteMapNode>

</siteMap>

To be valid, your site map must begin with the root <siteMap> node, followed by a single <siteMapNode> element, representing the default home page. You can nest other <siteMapNode> elements in the root <siteMapNode> as many layers deep as you want. Each site map node should have a title, description, and URL, as shown here:

<siteMapNode title="Home" description="Home" url="~/default.aspx">

In this example, the URL uses the new ~/ syntax, which indicates the root of the web application. This style isn’t necessary, but it is strongly recommended, as it ensures that your site map links are interpreted correctly regardless of the current folder.

You can now use the <siteMapNode> to create a site map. The only other restriction is that you can’t create two site map nodes with the same URL.

Note The restriction to avoid duplicate URLs is not baked into the navigation system. It’s simply required by the XmlSiteMapProvider, because the XmlSiteMapProvider uses the URL as a unique key. If you create your own site map provider or use a third-party provider, you may allow different URLs and require separate key information. However, you can’t get around the rule that every site must begin with one root node, because that’s implemented in the base SiteMapProvider class. (As you’ll see shortly, you still have options for tailoring the display of the site map tree, but you must start with a single home node.)

Here’s a sample site map:

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <siteMapNode title="Home" description="Home" url="~/default.aspx">

<siteMapNode title="Products" description="Our products" url="~/Products.aspx">

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

C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

569

url="~/Services.aspx">

<siteMapNode title="Training" description="Training classes" url="~/Training.aspx" />

<siteMapNode title="Consulting" description="Consulting services" url="~/Consulting.aspx" />

<siteMapNode title="Support" description="Support plans" url="~/Support.aspx" />

</siteMapNode>

</siteMapNode>

</siteMap>

Tip In this example, the Products and Services nodes have URLs, which means they are clickable (and take the user to specific pages). However, if you simply want to use these nodes as categories to arrange other links, just omit the url attribute. You’ll still see the node in your bound controls; it just won’t be rendered as a link.

Binding to a Site Map

Once you’ve defined the Web.sitemap file, you’re ready to use it in a page. This is a great place to use master pages so that you can define the navigation controls as part of a template and reuse them with every page. Here’s how you might define a basic structure in your master page that puts navigation controls on the left:

<form id="form1" runat="server"> <table>

<tr>

<td style="width: 226px;vertical-align: top;"> <!-- Navigation controls go here. -->

</td>

<td style="vertical-align: top;">

<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server" /> </td>

</tr>

</table>

<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" /> </form>

Then, create a child with some simple static content:

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">

<br /> <br />

Default.aspx page (home). </asp:Content>

The only remaining task is to choose the controls you want to use to display the site map data. One all-purpose solution is the TreeView control. You can add the TreeView and bind it to the SiteMapDataSource in the master page using the DataSourceID, as shown here:

<asp:TreeView ID="treeNav" runat="server" DataSourceID="SiteMapDataSource1" />

Alternatively, you could use the fly-out Menu control just as easily:

<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1" />

570 C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

Figure 16-9 shows both options.

Figure 16-9. TreeView and Menu navigation

You can do a lot more to customize the appearance of your navigation controls and the processing of your site map. You’ll consider these more advanced topics in the following sections.

Breadcrumbs

ASP.NET actually defines three navigation controls: the TreeView, Menu, and SiteMapPath. The SiteMapPath provides breadcrumb navigation, which means it shows the user’s current location and allows the user to navigate back up the hierarchy to a higher level using links. Figure 16-10 shows an example with a SiteMapPath control when the user is on the Software.aspx page. Using the SiteMapPath control, the user can return to the Products.aspx page or the Home.aspx page.

C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

571

Figure 16-10. Breadcrumb navigation with SiteMapPath

Here’s how you define the SiteMapPath control:

<asp:SiteMapPath ID="SiteMapPath1" runat="server" />

The SiteMapPath control is useful both for an at-a-glance view that provides the current position and for a way to move up the hierarchy. However, you always need to combine it with other navigation controls that let the user move down the site map hierarchy.

The SiteMapPath control is also thoroughly customizable. Table 16-6 lists some of its most commonly configured properties.

Table 16-6. SiteMapPath Appearance-Related Properties

Property

Description

ShowToolTips

Set this to false if you don’t want the description text to appear

 

when the user hovers over a part of the site map path.

ParentLevelsDisplayed

Sets the maximum number of parent levels that will be shown at

 

once. By default, this setting is -1, which means all levels will be

 

shown.

RenderCurrentNodeAsLink

If true, the portion of the page that indicates the current page is

 

turned into a clickable link. By default, this is false because the

 

user is already at the current page.

PathDirection

You have two choices: RootToCurrent (the default) and

 

CurrentToRoot (which reverses the order of levels in the path).

PathSeparator

Indicates the characters that will be placed between each level in

 

the path. The default is the greater-than (>) symbol. Another

 

common path separator is the colon (:).

 

 

For even more control, you can configure the SiteMapPath control with styles or even redefine the controls and HTML with templates (see Table 16-7).

572 C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

Table 16-7. SiteMapPath Styles and Templates

Style

Template

Applies To

NodeStyle

NodeTemplate

All parts of the path except the root and

 

 

current node.

CurrentNodeStyle

CurrentNodeTemplate

The node representing the current page.

RootNodeStyle

RootNodeTemplate

The node representing the root. If the root

 

 

node is the same as the current node, the

 

 

current node template or styles are used.

PathSeparatorStyle

PathSeparatorTemplate

The separator between each node.

 

 

 

For example, the following SiteMapPath uses an arrow image as a separator and a fixed string of bold text for the root node. The final part of the path, which represents the current page, is italicized.

<asp:SiteMapPath ID="SiteMapPath1" runat="server"> <PathSeparatorTemplate>

<asp:Image ID="Image1" ImageUrl="~/images/arrow.jpg" runat="server" GenerateEmptyAlternateText="True" />

</PathSeparatorTemplate>

<RootNodeTemplate>

<b>Root</b>

</RootNodeTemplate>

<CurrentNodeTemplate>

<i><asp:Label ID="Label1" runat="server" Text='<%# Eval("title") %>'> </asp:Label></i>

</CurrentNodeTemplate>

</asp:SiteMapPath>

Notice how the CurrentNodeTemplate uses a data binding expression to bind to the title property of the current node. You can also get the url and description attributes that you declared in the site map file in the same way.

Binding Portions of a SiteMap

In the examples so far, the page controls replicate the structure of the site map file exactly. However, this isn’t always what you want. For example, you might not like the way the Home node sticks out because of the XmlSiteMapProvider rule that every site map must begin with a single root. To clean this up, you can set the SiteMapDataSource.ShowStartingNode property to false. Then, modify the site map file so it defines the Home entry in the first group of pages. (The root node won’t be shown, so you can use a dummy node with any URL for it.)

Here’s the revised site map:

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0"> <siteMapNode title="Root" description="Root" url="~/">

<siteMapNode title="Home" description="Home" url="~/default.aspx"/> <siteMapNode title="Products" description="Our products"

url="~/Products.aspx">

...

</siteMapNode>

</siteMap>

Figure 16-11 shows the nicer result.

C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

573

Figure 16-11. A site map without the root node

This example shows how you can skip the root node. Another option you have is to show just a portion of the complete site map, starting from the current node. For example, you might use a control such as the TreeView to show everything in the hierarchy starting from the current node. If the user wants to move up a level, they could use another control (such as a SiteMapPath).

To implement this design, simply set the SiteMapDataSource.StartFromCurrentNode property to true. The SiteMapPath will still show the complete hierarchy, allowing the user to move up to a higher-level page. However, other bound controls will show only pages beneath the current page, allowing the user to move down the hierarchy. You still have the choice of whether to use ShowStartingNode, but now it determines whether you show the current node (because that’s the starting point for the navigation tree).

Figure 16-12 shows an example where both StartFromCurrentNode and ShowStartingNode are true. The current page is Software.aspx. The SiteMapPath shows higher-level pages, and the TreeView shows the nodes underneath the Software.aspx node.

Figure 16-12. Binding to child nodes only

For this technique to work, ASP.NET must be able to find a page in the Web.sitemap file that matches the current URL. Otherwise, it won’t know where the current position is, and it won’t provide any navigation information to the bound controls.

574 C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

The SiteMapDataSource has two more properties that can help you configure the navigation tree: StartingNodeOffset and StartingNodeUrl.

StartingNodeUrl is the easiest to understand—it takes the URL of the node that should be the first node in the tree. This value must match the url attribute in the Web.sitemap file exactly. For example, if you specify a StartingNodeUrl of "~/home.aspx", then the first node in the tree is the Home node, and you will see only nodes underneath that node.

The StartingNodeUrl property is particularly useful if you want to vary between a small number of different site maps (say, fewer than ten). The ideal solution is to define multiple site map files and bind to the one you want to use. Unfortunately, the default XmlSiteMapProvider supports only a single site map file, so you need to find a different mechanism. In this case, the solution is to separate the different site maps into distinct branches of the Web.sitemap file.

For example, imagine you want to have a dealer section and an employee section on your website. You might split this into two different structures and define them both under different branches in the same file, like this:

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode title="Root" description="Root" url="~/">

<siteMapNode title="Dealer Home" description="Home" url="~/default.aspx">

...

</siteMapNode>

<siteMapNode title="Employee Home" description="Home" url="~/default_emp.aspx">

...

</siteMapNode>

</siteMapNode>

</siteMap>

Now, to bind the menu to the dealer view, you set the StartingNodeUrl property to "~/default.aspx". You can do this programmatically or, more likely, by creating an entirely different master page and implementing it in all your dealer pages. In your employee pages, you set StartingNodeUrl property to "~/default_emp.aspx". This way, you’ll show only the pages under the Dealer Home branch of the site map.

You can even make your life easier by breaking a single site map into separate files using the siteMapFile attribute, like this:

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode title="Root" description="Root" url="~/">

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

</siteMapNode>

</siteMap>

Even with this technique, you’re still limited to a single site map tree, and it always starts with the Web.sitemap file. However, you can manage your site map more easily because you can factor some of its content into separate files.

Note This technique is greatly limited because the XmlSiteMapProvider doesn’t allow duplicate URLs. That means there’s no way to reuse the same page in more than one branch of a site map. Although you can try to work around this problem by creating different URLs that are equivalent (for example, by adding extra query string parameters on the end), this raises more headaches. If these limitations won’t work in your scenario, the best approach is to design your own site map provider.

The SiteMapDataSource.StartingNodeOffset property takes the most getting used to. It takes an integer that instructs the SiteMapDataSource to move that many levels down the tree (if the number

C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

575

is positive) or up the tree (if the number is negative). The important detail that’s often misunderstood is that when the SiteMapDataSource moves down the tree, it moves toward the current node. If it’s already at the current node, or your offset takes it beyond the current node, the SiteMapDataSource won’t know where to go, and you’ll end up with a blank navigation control.

To understand how this works, it helps to consider an example. Imagine you’re at this location in a website:

Home > Products > Software > Custom > Contact Us

If the SiteMapDataSource is starting at the Home node (the default), and you apply a StartingNodeOffset of 2, it will move down the tree two levels and bind this portion:

Software > Custom > Contact Us

That means you’ll be able to jump to any links in the Software or Custom groups, but you won’t be able to go anywhere else (at least not without stepping up a level first or clicking another control).

If you click the Custom link, the bound control will now show a tree with this information:

Products > Software > Custom

Now, what happens if you repeat the same test but set the site map provider to begin on another node? Consider what happens if you set StartFromCurrentNode to true. ASP.NET tries to move two levels down the hierarchy, starting from Contact Us. But because it’s already at the current page, it has nowhere to go and can’t get any navigational information.

On the other hand, if you set StartFromCurrentNode to true and use a StartingNodeOffset of -2, the SiteMapDataSource will move up two levels from Contact Us and bind this tree:

Software > Custom > Contact Us

It may take a bit of experimenting to decide the right combination of SiteMapDataSource settings that you want to use.

Programmatic Navigation

You aren’t limited to no-code data binding in order to display navigation hierarchies. You can interact with the navigation information programmatically. Two reasons exist for using programmatic navigation:

To change the display of the page: For example, you can retrieve the current node information and use that to configure details such as the page heading and title.

To implement different navigation logic: For example, you might want to display just a portion of the full list of child nodes for the current page in a newsreader, or you might want to create previous/next navigation buttons.

The site map API is remarkably straightforward. To use it, you need to work with two classes from the System.Web namespace. The starting point is the SiteMap class, which provides the static properties CurrentNode (the site map node representing the current page) and RootNode (the root site map node). Both of these properties return a SiteMapNode object. Using the SiteMapNode, you can retrieve information from the site map, including the title, description, and URL values. You can branch out to consider related nodes using the navigational properties in Table 16-8.

Note You can also search for nodes using the methods of the current SiteMapProvider object, which is available through the SiteMap.Provider static property. For example, the SiteMap.Provider.FindSiteMapNode() method allows you to search for a node by its URL.

576 C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

Table 16-8. SiteMapNode Navigational Properties

Property

Description

ParentNode

Returns the node one level up in the navigation hierarchy, which contains the

 

current node. On the root node, this returns a null reference.

ChildNodes

Provides a collection of all the child nodes. Check the HasChildNodes property

 

to determine if there are child nodes.

PreviousSibling

Returns the previous node that’s at the same level (or a null reference if no

 

such node exists).

NextSibling

Returns the next node that’s at the same level (or a null reference if no such

 

node exists).

 

 

To see this in action, consider the following code, which configures two labels on a page to show the heading and description information retrieved from the current node:

protected void Page_Load(object sender, EventArgs e)

{

lblHead.Text = SiteMap.CurrentNode.Title; lblDescription.Text = SiteMap.CurrentNode.Description;

}

The next example is a little more ambitious. It implements the previous/next links, which allow the user to traverse an entire set of subnodes. The code checks for the existence of sibling nodes, and if there aren’t any in the required position, it simply hides the links.

protected void Page_Load(object sender, EventArgs e)

{

if (SiteMap.CurrentNode.NextSibling != null)

{

lnkNext.NavigateUrl = SiteMap.CurrentNode.NextSibling.Url; lnkNext.Visible = true;

}

else

{

lnkNext.Visible = false;

}

}

Binding Other Controls

The TreeView and MenuView are two navigation controls that show hierarchical navigation information. (Both the TreeView and MenuView are described in more detail later in this chapter.) However, you aren’t limited to these two controls—you can also use any ASP.NET control that supports data binding, from the ListBox to the GridView.

For example, you can bind the navigation information to a template in a rich data control and use data binding expression to extract the title, description, and URL information. Here’s an example with a GridView:

<asp:GridView ID="listNavLinks" runat="server" DataSourceID="SiteMapDataSource1" AutoGenerateColumns="false" ShowHeader="False" BackColor="Linen" CellPadding="5"> <Columns>

<asp:TemplateField>

<ItemTemplate>

<a href='<%# Eval("Url") %>'><%# Eval("Title") %></a> <br />

C H A P T E R 1 6 W E B S I T E N AV I G AT I O N

577

<%# Eval("Description") %> </ItemTemplate>

</asp:TemplateField>

</Columns>

</asp:GridView>

Figure 16-13 shows the result.

Figure 16-13. Showing navigation links in a GridView template

The only limitation in this example is that it shows links nested underneath the current page. It doesn’t provide links to travel back up. You would need to add other controls to provide this functionality. You can use the SiteMapPath control along with the GridView, or you can use the SiteMap API. For example, you can use a LinkButton that, when clicked, runs this code to go up one level in the hierarchy:

protected void cmdUp_Click(object sender, EventArgs e)

{

Response.Redirect(SiteMap.CurrentNode.ParentNode.Url);

}

Unfortunately, you have no way to bind to nodes further down the hierarchy, as the SiteMapDataSource control doesn’t support the XPath syntax demonstrated in Chapter 12. However, you can embed a nested control and bind it programmatically, using the same technique that’s described in Chapter 10 in the “A Parent/Child View in a Single Table” section.

Adding Custom Site Map Information

In the site maps you’ve seen so far, the only information that’s provided for a node is the title, description, and URL. This is the bare minimum of information that you’ll want to use. However, the schema for the XML site map is open, which means you’re free to insert custom attributes with your own data.

You might want to insert additional node data for a number of reasons. This additional information might be descriptive information that you intend to display or contextual information that describes how the link should work. For example, you could add attributes that specify a target frame or indicate that a link should be opened in a pop-up window. The only catch is that it’s up to you to act on the information later. In other words, you need to configure your user interface so it uses this extra information.