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

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

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

548 C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

Figure 15-15. A master page and content page that use a table

Master Pages and Relative Paths

One quirk that can catch unsuspecting developers is the way that master pages handle relative paths. If all you’re using is static text, this issue won’t affect you. However, if you’ve added <img> tags or any other HTML tag that points to another resource, problems can occur.

The problem shows up if you place the master page in a different directory from the content page that uses it. This is a recommended best practice for large websites. In fact, Microsoft encourages you to use a dedicated folder for storing all your master pages. However, if you’re not suitably careful, this can cause problems when you use relative paths.

For example, imagine you put a master page in a subfolder named MasterPages and add the following <img> tag to the master page:

<img src="banner.jpg" />

Assuming the file \MasterPages\banner.jpg exists, this appears to work fine. The image will even appear in the Visual Studio design environment. However, if you create a content page in another subfolder, the path is interpreted relative to that folder. If the file doesn’t exist there, you’ll get a broken link instead of your graphic. Even worse, you could conceivably get the wrong graphic if another image has the same filename.

This problem occurs because the <img> tag is ordinary HTML. As a result, ASP.NET won’t touch it. Unfortunately, when ASP.NET builds your content page, this tag is no longer appropriate.

To solve your problem, you could try to think ahead and write your URL relative to the content page where you want to use it. But this creates confusion, limits where your master page can be used, and has the unwelcome side effect of displaying your master page incorrectly in the design environment.

Another quick fix is to make your image tag into a server-side control, in which case ASP.NET will fix the mistake:

<img src="banner.jpg" runat="server"/>

This works because ASP.NET uses this information to create an HtmlImage server control. This object is created after the Page object for the master page is instantiated. At this point, ASP.NET interprets all the paths relative to the location of the master page. You could use the same technique to fix <a> tags that provide relative links to other pages.

You can also use the root path syntax and start your URL with the ~ character. For example, this <img> tag points unambiguously to the banner.jpg file in the MasterPages subfolder of the website:

<img src="~/MasterPages/banner.jpg" runat="server" />

C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

549

Unfortunately, this syntax works only with server-side controls. If you want a similar effect with ordinary HTML, you need to change the link to a full relative path incorporating your domain name. This makes for ugly, unportable HTML, and it’s not recommended.

Master Pages and Formatting

Master pages give you another technique you can use to apply consistent formatting across your entire website. For example, if you want a particular theme to always be used with a master page, simply set the Theme or StyleSheetTheme attribute of the Master directive. That way every content page gets the linked theme without any extra work required. Linking themes to master pages is a useful technique—it’s easier than setting the theme for each page and not as clumsy as trying to apply it across an entire website through the web.config file.

Master pages provide a few other options for standardizing formatting. For example, you can link to a stylesheet without using themes by adding a <link> element in the <head> section of the master page.

You can also use a more fine-grained model and have your master page help you apply different formatting to different sections of a content page. All you need to do is set the appropriate foreground and background colors, fonts, and alignment options using container tags in the master page. For example, you might set these on a table, a table cell, or a <div> tag. The information from the content page can then flow seamlessly into these containers, acquiring the appropriate style attributes automatically.

Applying Master Pages Through a Configuration File

It’s worth noting that you can also apply a master page to all the pages in your website at once using the web.config file. All you need to do is add the <pages> attribute and set its masterPageFile attribute, as shown here:

<configuration>

<system.web>

<pages masterPageFile ="SiteTemplate.master"/> </system.web>

</configuration>

The problem is that this approach tends to be quite inflexible. Any web page you have that doesn’t play by the rules (for example, includes a root <html> tag or defines a content region that doesn’t correspond to a ContentPlaceHolder) will be automatically broken. If you must use this feature, don’t apply it site-wide. Instead, create a subfolder for your content pages, and create a web.config file in just that subfolder to apply the master page.

Note Even if a master page is applied through the web.config, you have no guarantee that an individual page won’t override your setting by supplying a MasterPageFile attribute in the Page directive. And if the MasterPageFile attribute is specified with a blank string, the page won’t have any master page at all, regardless of what the web.config file specifies.

Advanced Master Pages

Using what you’ve learned, you can create and reuse master pages across your website. However, you can use other tricks and techniques to refine the way master pages work. In the following sections, you’ll see how to interact with a master page from your content, how to set master pages dynamically, and how to nest one master page inside another.

550 C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

Specifying a Title and Metatags for a Content Page

As you’ve seen, the master page always specifies the basic HTML skeleton of the page, including the outermost <html> tag and the <head> portion. This raises a potential problem. Namely, what if the content page needs to supply information for the <head> section of the page, such as a page title?

One option is to define a ContentPlaceHolder in the <head> section of the page to wrap the appropriate tags. However, this approach isn’t valid, because the ContentPlaceHolder, like all ASP.NET controls, needs to be nested inside a server-side <form> tag. Fortunately, ASP.NET provides a solution. You can set the page title through the Title attribute of the Page directive.

The Title attribute overrides the title that’s specified in the master page with something more appropriate for the particular content page. Here’s an example:

<%@ Page Language="C#" MasterPageFile="~/SiteTemplate.master" AutoEventWireup="true" CodeFile="SimpleContentPage.aspx.cs" Inherits="SimpleContentPage_aspx" Title="Content Page" %>

This works only as long as the master page has the runat="server" attribute in the <head> tag, which is the default.

This approach isn’t any help if you need to override other ingredients from the <head> section, such as the metatags or style tags. However, this problem has a solution. You can modify the <head> element programmatically. To do this, you need to retrieve a reference to the Page object for the master page, which you can access from the current page like this:

Page masterPage = base.Master.Page

Of course, the base keyword is optional, but this makes it clear that Master is a property that’s built into the base Page class. If the current page doesn’t use a master page, the Master property returns null.

Using the Page object, you can drill down to the server-side HtmlHead control, as described in Chapter 3. Here’s an example that changes the title and adds metadata tags using this technique:

base.Master.Page.Header.Title = "Content Page"; base.Master.Page.Header.Metadata.Add("Keywords", "great,cool,revolutionary"); base.Master.Page.Header.Metadata.Add("Description", "A truly great website.");

Typically, you’d execute this code when the content page’s Page.Load event fires.

Tip Sometimes you might use initialization code in both the master page and the content page. In this situation, it’s important to understand the order that the respective events fire. ASP.NET begins by creating the master page controls and then the child controls for the content page. It then fires the Page.Init event for the master page and follows it up by firing the Page.Init event for the content page. The same step occurs with the Page.Load event. Thus, customizations that you perform in the content page (such as changing the page title) will take precedence over changes you make at the same stage in the master page, if they conflict.

Interacting with the Master Page Class

One issue with master pages is how their model assumes you either want to copy something exactly across every page (in which case you include it in the master page) or vary it on each and every page (in which you add a ContentPlaceHolder for it and include the information in each content page).

This distinction works well for many pages, but it runs into trouble if you want to allow a more nuanced interaction between the master page and content pages.

For example, you might want the master page to give a choice of three display modes. The content page would then choose the correct display mode, which would change the appearance of the

C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

551

master page. However, the content page shouldn’t have complete freedom to change the master page indiscriminately. Instead, anything other than these three presets should be disallowed.

To enable scenarios such as these, you need some level of programmatic interaction between the content page and the master page. This isn’t too difficult, because you can access the current instance of your master page using the Page.Master property, as described in the previous section.

The first step in allowing interaction between your content page and master page is to add public properties or methods to your master page class. The content page can then set these properties or call these methods accordingly. For example, maybe you want to make the banner text customizable (as shown in a previous example) but you don’t want to let the content page insert any type of content there. Instead, you want to restrict it to a single descriptive string. To accomplish this, you can add a server-side label control to the header and provide access to that control through a BannerText property in the master page class:

public string BannerText

{

get { return lblTitleContent.Text; } set { lblTitleContent.Text = value; }

}

The content page can now change the text. The only caveat is that the Master property returns an object that’s typed as the generic MasterPage class. You need to cast it to your specific master page class to get access to any custom members you’ve added.

protected void Page_Load(object sender, EventArgs e)

{

CustomizableMasterPage_master master = (CustomizableMasterPage_master)Master;

master.BannerText = "Content Page #1";

}

Another way to get strongly typed access to the master page is to add the MasterType directive to the content page. All you need to do is indicate the virtual path of the corresponding .master file:

<%@ MasterType VirtualPath="~/SiteTemplate.master" %>

Now you can use simpler strongly typed code when you access the master page:

protected void Page_Load(object sender, EventArgs e)

{

Master.BannerText = "Content Page #1";

}

You should note one point about these examples. When you navigate from page to another, all the web-page objects are re-created. This means that even if you move to another content page that uses the same master page, ASP.NET creates a different instance of the master page object. As a result, the Text property of the Label control in the header is reset to its default value (a blank string) every time the user navigates to a new page. To change this behavior, you need to store the information in another location (such as a cookie) and write initialization code in the master page to check for it.

You can also get access to an individual control on a master page through brute force. The trick is to use the MasterPage.FindControl() method to search for the object you want based on its unique name. When you have the control, you can then modify it directly. Here’s an example that uses this technique to look for a label:

Label lbl = Master.FindControl("lblTitleContent") as Label if (lbl != null)

{

lbl.Text = "Content Page #1";

}

552 C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

Of course, this type of interaction breaks all the rules of proper class-based design and encapsulation. If you really need to access a control in a master page, you are far better off wrapping it (or, ideally, just the properties you’re interested in) by adding properties to your master page class. That way, the interaction between the content page and the master page is clear, documented, and loosely coupled. If your content page tinkers directly with the internals of another page, it’s likely to lead to fragile code models with dependencies that break when you edit the master page.

Dynamically Setting a Master Page

Sometimes you might want to change your master page on the fly. This might occur in a few cases:

Several types of users exist, and you want to adjust the complexity of the layout or the visible features according to the user. You may perform this customization based on user information managers have specified, or you might give the users the ability to set their own preferences.

You are in partnership with another company, and you need your website to adjust itself to have a different look and layout accordingly. For example, you might cobrand your website, providing the same features with two or more different layouts.

Changing the master page programmatically is easy. All you need to do is set the Page.MasterPageFile property. The trick is that this step needs to be completed in the Page.Init event stage. After this point, attempting to set this property causes an exception.

You can implement this technique in much the same way that you implemented dynamic themes earlier in this chapter. However, this technique has a potential danger—a content page isn’t necessarily compatible with an arbitrary master page. If your content page includes a Content tag that doesn’t correspond to a ContentPlaceHolder in the master, an error will occur. To prevent this problem, you need to ensure that all the master pages you set dynamically include the same placeholders.

Nesting Master Pages

You can nest master pages so that one master page uses another master page. This is not used too often, but it could allow you to standardize your website to different degrees. For example, you might have two sections of your website. Each section might warrant its separate navigation controls. However, both sections may need the same header. In this case, you could create a top-level master page that adds the header. Here’s an example:

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="NestedMasterRoot.master.cs" Inherits="NestedMasterRoot_master" %>

<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">

<title>Untitled Page</title> </head>

<body bgcolor="#ccffff">

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

<h1>The Root</h1>

<asp:ContentPlaceHolder id="RootContent" runat="server"> </asp:ContentPlaceHolder >

</div>

</form>

</body>

</html>

C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

553

Next, you would create a second master page that uses the first master page (through the MasterPageFile attribute). This second master page would get the header from the first master page and could add the navigation controls in a panel on the left. Here’s an example:

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="NestedMasterSecondLevel.master.cs" Inherits="NestedMasterSecondLevel_master"

MasterPageFile="~/NestedMasterRoot.master"%>

<asp:Content ID="Content1" ContentPlaceHolderID="RootContent" Runat="Server"> <table width="100%" bgcolor="#ccff00">

<tr>

<td colspan="2">

<h2>The Second Level</h2> </td>

<tr>

<td width="200px"></td> <td bgcolor="white">

<asp:ContentPlaceHolder id="NestedContent" runat="server"> </asp:ContentPlaceHolder >

</td>

</tr>

</table>

</asp:Content>

Presumably, your goal would be to create more than one version of the second master page, one for each section of your website. These would acquire the same standard header.

Finally, each content page could use one of the second-level master pages to standardize its layout:

<%@ Page Language="C#" MasterPageFile="~/NestedMasterSecondLevel.master" AutoEventWireup="true" CodeFile="NestedContentPage.aspx.cs" Inherits="NestedContentPage_aspx" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="NestedContent" Runat="Server"> <br />This is the nested content!<br />

</asp:Content>

Figure 15-16 shows the result.

Figure 15-16. A content page that uses a nested master page

554 C H A P T E R 1 5 T H E M E S A N D M A S T E R PA G E S

You can use as many layers of nested master pages as you want. However, be careful when implementing this approach—although it sounds like a nifty way to make a modular design, it can tie you down more than you realize. For example, you’ll need to reword your master page hierarchy if you decide later that the two website sections need similar but slightly different headers. For that reason, it might be better to use only one level of master pages and copy the few common elements. In most cases, you won’t be creating many master pages, so this won’t add a significant amount of duplication.

Caution Another significant issue with nested master pages is the lack of design-time support. In the previous example, you’ll be able to access the design surface only for the root master page. You’ll need to code the secondlevel master page and the content page by hand.

Summary

In this chapter you tackled two key enhancements that were introduced in ASP.NET 2.0: themes and master pages. Armed with these tools, you can create a complete, integrated web application that has a unified look and feel and a consistent layout. In the next chapter, you’ll learn how to add navigation controls to the mix.

C H A P T E R 1 6

■ ■ ■

Website Navigation

Navigation is a fundamental component of any website. ASP.NET 1.x had plenty of raw tools, but you were forced to cobble together your own navigation solutions. ASP.NET 2.0 addresses this gap by introducing a wide range of navigation features.

In this chapter, you’ll tackle three core topics:

The MultiView and Wizard controls: These let you boil down a series of steps into a single page.

The new site map model: This lets you define the navigation structure of your website and bind it directly to rich controls. You’ll also learn how to extend this framework to support different types of controls and different site map storage locations.

The rich navigational controls: These include the TreeView and Menu. Although these controls aren’t limited to navigation, they’re an ideal match. In this chapter, you’ll learn about their wide range of features.

Using these controls, the site map model, and master pages, you can build a complete navigation system with minimal effort. Best of all, ASP.NET cleanly separates the data (the information about the structure of your website) from its implementation (the navigational controls). That means you can reorganize, replace, and rename web pages without disturbing your website or editing any code. All you need to do is make the corresponding changes to your application’s site map file.

Note The features discussed in this chapter are all new in ASP.NET 2.0. That means if you’re a skilled ASP.NET 1.x developer, this is a chapter worth reading in detail.

Pages with Multiple Views

Most websites split tasks across several pages. For example, if you want to add an item to your shopping cart and take it to the checkout in an e-commerce site, you’ll need to jump from one page to another. This is the cleanest approach, and it’s easy to program—provided you use some sort of state management technique (from query strings to session state) to transfer information from one page to another.

In other situations, you might want to embed the code for several different pages inside a single page. For example, you might want to provide several views of the same data (such as a gridbased view and a chart-based view) and allow the user to switch from one view to the other without

555

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

leaving the page. Or, you might want to handle a small multistep task (such as supplying user information for an account sign-up process), without worrying about how to transfer the relevant information.

Tip From the user’s point of view, it probably doesn’t make much difference whether you use multiple pages or a page with multiple views. In a well-designed site, the only difference the user will see is that the multiple view approach keeps the same URL. The prime difference is the coding model. With multiple pages, you get improved separation but extra work in determining how the pages should interact (the way they share or transmit information). With multiple views, you lose your separation but get easier coding for small, nondivisible tasks.

In ASP.NET 1.x, the only way to model a page with multiple views was to add several Panel controls to a page so that each panel represents a single view or a single step. You can then set the Visible property of each Panel so that you see only one at a time. The problem with this approach is that it clutters your page with extra code for managing the panels. Additionally, it’s not very robust—with a minor mistake, you can end up with two panels showing at the same time.

In ASP.NET 2.0, there’s no need to design your own multiple view system from scratch. Instead, you can use one of two higher-level controls that make these designs much easier—the MultiView and the Wizard.

The MultiView Control

The MultiView is the simpler of the two multiple view controls. Essentially, the MultiView gives you a way to declare multiple views and show only one at a time. It has no default user interface—you get only whatever HTML and controls you add. The MultiView is equivalent to the custom panel approach explained earlier.

Creating a MultiView is suitably straightforward. You add the <asp:MultiView> tag to your .aspx page file and then add one <asp:View> tag inside it for each separate view.

<asp:MultiView ID="MultiView1" runat="server"> <asp:View ID="View1" runat="server">...</asp:View> <asp:View ID="View2" runat="server">...</asp:View> <asp:View ID="View3" runat="server">...</asp:View>

</asp:MultiView>

Inside the <asp:View> tag, you add the HTML and web controls for that view.

<asp:MultiView ID="MultiView1" runat="server">

<asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> <asp:View ID="View1" runat="server">

<b>Showing View #1<br /> <br />

<asp:Image ID="Image1" runat="server" ImageUrl="~/cookies.jpg" /></b>

</asp:View>

<asp:View ID="View2" runat="server"> <b>Showing View #2</b><br />

<br />

Text content. </asp:View>

<asp:View ID="View3" runat="server"> <b>Showing View #3</b><br />

<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

557

<asp:Calendar ID="Calendar1" runat="server"></asp:Calendar> </asp:View>

</asp:MultiView>

</asp:MultiView>

Tip You can also add views programmatically (like any other control) by instantiating a new view object and adding it to the MultiView with the Add() or AddAt() methods of the Views collection.

Visual Studio shows all your views at design time, one after the other (see Figure 16-1). You can edit these regions in the same way you design any other part of the page.

Figure 16-1. Designing multiple views

The MultiView.ActiveViewIndex determines what view will be shown. This is the only view that’s rendered in the page. The default ActiveIndex value is -1, which means no view is shown. You need to set the ActiveIndex programmatically.

One option is to use a list control that lets users choose from the full list of views. Here’s some sample code that binds the list of views to a list box:

protected void Page_Load(object sender, EventArgs e)

{

if (!Page.IsPostBack)

{

DropDownList1.DataSource = MultiView1.Views;