ASPNET MasterPages Tutorial 04
ASPNET MasterPages Tutorial 04
Introduction
In all the examples we've seen thus far, the master and content pages have been located in
the same folder (the website's root folder). But there's no reason why the master and
content pages must be in the same folder. You can certainly create content pages in
subfolders. Similarly, you might create a ~/MasterPages/ folder where you place your site's
master pages.
One potential issue with placing master and content pages in different folders involves
broken URLs. If the master page contains relative URLs in hyperlinks, images, or other
elements, the link will be invalid for content pages that reside in a different folder. In this
tutorial we examine the source of this problem as well as workarounds.
The src attribute value in the <img> element is a relative URL because it does not start with
/ or http://. In short, the src attribute value tells the browser to look in the Images
subfolder for a file named PoweredByASPNET.gif.
When visiting a content page, the above markup is sent directly to the browser. Take a
moment to visit About.aspx and view the HTML source sent to the browser. You will find
that the exact same markup in the master page was sent to the browser.
<div id="footerContent">
<img src="Images/PoweredByASPNET.gif" alt="Powered by ASP.NET!" />
</div>
If the content page is in the root folder (as is About.aspx) everything works as expected
because there is an Images subfolder relative to the root folder. However, things break
down if the content page is in a different folder than the master page. To illustrate this,
create a subfolder named Admin. Next, add a content page named Default.aspx to the
Admin folder, making sure to bind the new page to the Site.master master page.
Note: In the Specifying the Title, Meta Tags, and Other HTML Headers in the Master
Page tutorial we created a custom base page class named BasePage that
automatically set the content page's title (if it was not explicitly assigned). Don't
forget to have the newly created page's code-behind class derive from BasePage so
that it can utilize this functionality.
After you have created this content page, your Solution Explorer should look similar to
Figure 1.
Figure 01: A New Folder and ASP.NET Page Have Been Added to the Project
Next, update the Web.sitemap file to include a new <siteMapNode> entry for this lesson.
The following XML shows the complete Web.sitemap markup, which now includes the
addition of a third <siteMapNode> element.
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/Default.aspx" title="Home">
<siteMapNode url="~/About.aspx" title="About the Author" />
<siteMapNode url="~/MultipleContentPlaceHolders.aspx"
title="Using Multiple ContentPlaceHolder Controls" />
<siteMapNode url="~/Admin/Default.aspx"
title="Rebasing URLs" />
</siteMapNode>
</siteMap>
The newly created Default.aspx page should have four Content controls corresponding to
the four ContentPlaceHolders in Site.master. Add some text to the Content control
referencing the MainContent ContentPlaceHolder and then visit the page through a browser.
As Figure 2 shows, the browser cannot find the PoweredByASPNET.gif image file. What's
going on here?
The ~/Admin/Default.aspx content page is sent the same HTML for the footerContent
region as was the About.aspx page:
<div id="footerContent">
<img src="Images/PoweredByASPNET.gif" alt="Powered by ASP.NET!" />
</div>
Because the <img> element's src attribute is a relative URL, the browser attempts to look
for an Images folder relative to the web page's folder location. In other words, the browser
is looking for the image file Admin/Images/PoweredByASPNET.gif.
/ASPNET_MasterPages_Tutorial_04_CS/Images/PoweredByASPNET.gif
http://localhost:3908/ASPNET_MasterPages_Tutorial_04_CS/Images/PoweredB
yASPNET.gif
Take a moment to update the <img> element's src attribute to an absolute URL using one of
the forms shown above and then visit the ~/Admin/Default.aspx page through a browser.
This time the browser will correctly find and display the PoweredByASPNET.gif image file
(see Figure 3).
While hard coding in the absolute URL works, it tightly couples your HTML to the website's
server and folder location, which may change. Using an absolute URL of the form
http://localhost:3908/... is brittle because the port number preceding localhost is
selected automatically each time Visual Studio's built-in ASP.NET Development Web Server
is started. Similarly, the http://localhost part is only valid when testing locally. Once the
code is deployed to a production server, the URL base will change to something else, like
http://www.yourserver.com. The absolute URL in the form
/ASPNET_MasterPages_Tutorial_04_CS/... also suffers from the same brittleness because
oftentimes this application path differs between development and production servers.
The good news is that ASP.NET offers a method for generating a valid relative URL at
runtime.
The Control class's ResolveClientUrl method takes a URL and modifies it to a relative
URL appropriate for the web page on which the control resides. For example, calling
ResolveClientUrl("~/Images/PoweredByASPNET.gif") from About.aspx returns
Images/PoweredByASPNET.gif. Calling it from ~/Admin/Default.aspx, however, returns
../Images/PoweredByASPNET.gif.
Note: Because all ASP.NET server controls derive from the Control class, all server
controls have access to the ResolveClientUrl method. Even the Page class derives
from the Control class, meaning that you can use this method directly from your
ASP.NET pages' code-behind classes.
To fix the image markup in Site.master, replace the existing <img> element with an
ASP.NET Image Web control. Set the Image Web control's ID to PoweredByImage, its
ImageUrl property to ~/Images/PoweredByASPNET.gif, and its AlternateText property to
"Powered by ASP.NET!"
<div id="footerContent">
<asp:Image ID="PoweredByImage" runat="server"
ImageUrl="~/Images/PoweredByASPNET.gif" AlternateText="Powered by
ASP.NET!" />
</div>
After making this change to the master page, revisit the ~/Admin/Default.aspx page
again. This time the PoweredByASPNET.gif image file appears in the page (see Figure 3).
When the Image Web control is rendered it uses the ResolveClientUrl method to resolve
its ImageUrl property value. In ~/Admin/Default.aspx the ImageUrl is converted into an
appropriate relative URL, as the following snippet of HTML source shows:
<div id="footerContent">
<img id="ctl00_PoweredByImage" src="../Images/PoweredByASPNET.gif"
alt="Powered by ASP.NET!" style="border-width:0px;" />
</div>
Note: In addition to being used in URL-based Web control properties, the ~ can also
be used when calling the Response.Redirect and Server.MapPath methods, among
others. Also, the ResolveClientUrl method may be invoked directly from an
ASP.NET or master page's declarative markup, if needed; see Fritz Onion's blog entry
Using ResolveClientUrl in Markup.
Because this URL is relative, it will send the user to the Default.aspx page in the folder of
the content page they are visiting. To have this link always point to Default.aspx in the
root folder we need to replace the <a> element with a HyperLink Web control so that we can
use the ~ notation.
Remove the <a> element markup and add a HyperLink control in its place. Set the
HyperLink's ID to lnkHome, its NavigateUrl property to ~/Default.aspx, and its Text
property to "Master Pages Tutorials."
<div id="topContent">
<asp:HyperLink ID="lnkHome" runat="server"
NavigateUrl="~/Default.aspx" Text="Master Pages Tutorials" />
</div>
That's it! At this point all the URLs in our master page are properly based when rendered by
a content page regardless of what folders the master page and content page are located in.
While the <link> element's href attribute is relative, it's automatically converted to an
appropriate path at runtime. As we discussed in the Specifying the Title, Meta Tags, and
Other HTML Headers in the Master Page tutorial, the <head> region is actually a server-side
control, which enables it to modify the contents of its inner controls when it is rendered.
To verify this, revisit the ~/Admin/Default.aspx page and view the HTML source sent to
the browser. As the snippet below illustrates, the <link> element's href attribute has been
automatically modified to an appropriate relative URL, ../Styles.css.
<head>
<title>
Default
</title>
<link href="../Styles.css" rel="stylesheet" type="text/css" />
</head>
Summary
Master pages very often include links, images, and other external resources that must be
specified via a URL. Because the master page and content pages might not exist in the
same folder, it is important to abstain from using relative URLs. While it is possible to use
hard coded absolute URLs, doing so tightly couples the absolute URL to the web application.
If the absolute URL changes - as it often does when moving or deploying a web application -
you'll have to remember to go back and update the absolute URLs.
The ideal approach is to use the tilde (~) to indicate the application root. ASP.NET Web
controls that contain URL-related properties map the ~ to the application root at runtime.
Internally, the Web controls use the Control class's ResolveClientUrl method to generate
a valid relative URL. This method is public and available from every server control (including
the Page class), so you can use it programmatically from your code-behind classes, if
needed.
Happy Programming!
Further Reading
For more information on the topics discussed in this tutorial, refer to the following
resources: