Académique Documents
Professionnel Documents
Culture Documents
NET
Problem. You need to use master pages along with code-behind in your ASP.NET website.
Master pages can be combined with code-behind in C# to simplify and ease maintenance of your
complex site. Solution. Here we look at how to use master pages in ASP.NET and get started
with examples.
<script runat="server">
</script>
8. Summary
You can dynamically generate pages, leading to fewer typos, with less content to manage. The
master page, content page and code-behind model can simplify and streamline your site. Code-
behind can separate complex logic from page markup.
Master pages are a great addition to the ASP.NET 2.0 feature set. Master pages help us
build consistent and maintainable user interfaces. Master pages, however, are not without their
quirks. Sometimes master page behavior is surprising, and indeed the very name master page can
be a bit misleading. In this article, we are going to examine some of the common problems
developers run into when using master pages, and demonstrate some practical advice for making
effective use of master pages. For an introduction to master pages, see "Master Pages In
ASP.NET 2.0".
To make use of master pages, we first need to understand how master pages work. Many of the
tips and traps covered later in this article revolve around understanding the magic behind master
pages. Let’s dig into these implementation details first.
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
The master page contains some common elements, like a head tag. The most important server-
side controls are the form tag (form1) and the ContentPlaceHolder (ContentPlaceHolder1). Let’s
also write a simple web form to use our master page.
<%@ Page Language="C#" MasterPageFile="~/Master1.master"
AutoEventWireup="true" Title="Untitled Page" %>
<asp:Content ID="Content1" Runat="Server"
ContentPlaceHolderID="ContentPlaceHolder1" >
<asp:Label ID="Label1" runat="server" Text="Hello, World"/>
</asp:Content>
The web form contains a single Content control, which in turn is the proud parent of a Label. We
can visualize what the object hierarchies would look like at runtime with the following diagram.
At this point, the page and master page are two separate objects, each with their own children.
When it comes time for the master page to do its job, the master page replaces the page’s
children with itself.
The master page’s next step is to look for Content controls in the controls formerly associated
with the page. When the master page finds a Content control that matches a ContentPlaceHolder,
it moves the controls into the matching ContentPlaceHolder. In our simple setup, the master page
will find a match for ContentPlaceHolder1, and copy over the Label.
All of this work occurs after the content page’s PreInit event, but before the content page’s Init
event. During this brief slice of time, the master page is deserving of its name. The master page
is in control - giving orders and rearranging controls. However, by the time the Init event fires
the master page becomes just another child control inside the page. In fact, the MasterPage class
derives from the UserControl class. I’ve found it useful to only think of master pages as masters
during design time. When the application is executing, it’s better to think of the master page as
just another child control.
The Pre_Init event we just mentioned is a key event to examine if we want to change the master
page file programmatically. This is the next topic for discussion.
To use this base class, we need to change our code-beside file classes to inherit from BaseClass
instead of System.Web.UI.Page. For web forms with inline code, we just need to change the
Inherits attribute of the @ Page directive.
<%@ Page Language="C#" MasterPageFile="~/Master1.master"
AutoEventWireup="true" Title="Untitled Page"
Inherits="BasePage" %>
The inheritance approach is flexible. If a specific page doesn’t want it’s master page set, it can
choose not to derive from BasePage. This is useful if different areas of an application use
different master pages. However, there may be times when we want an application to enforce a
specific master page. It could be the same type of scenario (we pull the master page name from a
database), but we don’t want to depend on developers to derive from a specific base class
(imagine a third party uploading content pages). In this scenario we can factor the PreInit code
out of the base class and into an HttpModule.
HttpModules sit in the ASP.NET processing pipeline and can listen for events during the
processing lifecycle. Modules are good solutions when the behavior you want to achieve is
orthogonal to the page processing. For instance, authentication, authorization, session state, and
profiles are all implemented as HttpModules by the ASP.NET runtime. You can plug-in and
remove these modules to add or discard their functionality. Here is a module to set the
MasterPageFile property on every Page object.
using System;
using System.Web;
using System.Web.UI;
Abstract Interaction
Now it’s time to have the master page and content page interact. There are different approaches
we can take to achieve interaction, but the best approaches are the ones that use the master page
for what it is: a user control. First, let’s look at how the content page can interact with the master
page.
Content Page to Master Page Interaction
Let’s imagine we want all of the pages in our application to have some text in a footer area. This
seems like the perfect job for a master page, so we will add a label control to our master.
<form id="form1" runat="server">
<div>
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
</asp:contentplaceholder>
</div>
</form>
The catch is, some content pages need to override the default footer text. Here is one approach
we can use from page’s Page_Load event handler.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As EventArgs)
End Sub
Use the above approach with extreme caution. FindControl is fragile, and will return null if
someone renames FooterLabel, or removes the control entirely. This problem can't be discovered
until runtime. FindControl also has some additional difficulties when INamingContainers are
involved - we will discuss this topic later.
A better approach is to establish a formal relationship between the master page and content page,
and take advantage of strong typing. Instead of the content page poking around inside the master
page, let’s have the master page expose the footer text as a property. We can add the following
code to our master page.
Public Property FooterText() As String
Get
Return FooterLabel.Text
End Get
Set(ByVal value As String)
FooterLabel.Text = value
End Set
End Property
The best way to use this property is to place a @ MasterType directive in our content page.
When the ASP.NET compiler sees the @ MasterType directive, it creates a strongly typed Master
property in our Page derived class.
<%@ Page Language="VB" MasterPageFile="~/Master1.master"
AutoEventWireup="true" %>
<%@ MasterType VirtualPath="~/Master1.master" %>
<script runat="server">
End Sub
</script>
This code is a cleaner and doesn’t depend on the magic string “FooterLabel”. If anyone ever
removes the control from the master page, or renames the control, we will have compilation
errors instead of runtime problems.
What if we have 2 different master pages in the application? In this scenario, we have a problem,
because the VirtualPath attribute supports only a single master page. We’ve tightly coupled our
page to a specific master. If we assign a MasterPageFile that does not match the MasterType, the
runtime will throw an exception.
Unable to cast object of type 'ASP.master2_master' to type 'ASP.master1_master'.
Fortunately, the @ MasterType directive doesn’t require us to use a VirtualPath, we can also
specify a type name. Once again we will turn to inheritance to solve this problem. If all the
content pages expect their master pages to have footer text, then let’s define a base class for the
master pages to inherit.
We can take one of two approaches with the base class. One approach is to use an abstract
(MustInherit) base class:
using System.Web.UI;
Our master pages must inherit from this base class and override the FooterText property.
<%@ Master Language="VB" Inherits="BaseMasterPage" %>
<script runat="server">
</script>
Now our page can use any master page that inherits from BaseMasterPage. All we need is an @
MasterType directive set to the base class. Instead of using a VirtualPath attribute, we use a
TypeName attribute and specify the name of the base class.
<%@ Page Language="VB" MasterPageFile="~/Master1.master"
AutoEventWireup="true" %>
<%@ MasterType TypeName="BaseMasterPage" %>
<script runat="server">
End Sub
</script>
The second approach is to use a concrete base class. This approach is possible only if we are sure
every master page will have a label with an ID of “FooterLabel”.
using System.Web.UI;
using System.Web.UI.WebControls;
With the above approach we can remove code from our master page – we don’t need to define
the FooterText property. If we are using code-beside files instead of inline script, we need to use
CodeFileBaseClass=”BaseMasterPage” in the @ Master directive to ensure ASP.NET can wire
up the base class’s Label field with the Label control.
Master Page To Content Page Interaction
Here is a case where the master part of the master page name can be misleading. The master
page sounds like a good place to put logic and code that will tell the page how to do something.
After all, a master page is the master, right? We now know that the master page is just another
child control. Ideally, the master page will remain passive. Instead of telling it’s parent page
what to do, the master page should tell a page when something interesting happenes, and let the
page decide what to do.
Let’s pretend every page in our application displays a report, and every page needs a button for
users to click and email the report. Putting a Button and a TextBox inside the master page seems
like a reasonable choice.
<asp:TextBox runat="server" id="EmailAddressBox" />
<asp:Button runat="server" ID="SendEmailButton"
OnClick="SendEmailButton_Click" />
What happens when the user clicks the button? We can choose from the following options:
• Handle the Click event in the master page, and have the master page email
the report.
• Expose the Button and TextBox as public properties of the master page, and
let the content page subscribe to the click event (and email the report).
• Define a custom SendEmail event, and let each page subscribe to the event.
The first approach can be ugly because the master page will need to call methods and properties
on the page. Master pages are about layout, we don’t want to clutter them with knowledge of
reports and specific pages.
The second approach is workable, but it tightly couples the page to the master. We might change
the UI one day and use a DropDownList and a Menu control instead of a TextBox and Button, in
which case we’ll end up changing all of our pages.
The third approach decouples the master page and content page nicely. The page won’t need to
know what controls are on the master page, and the master page doesn’t have to know anything
about reports, or the content page itself. We could start by defining the event in a class library, or
in a class file in App_Code.
using System;
We can raise this event from a master page base class (if we have one), or from the master page
itself. In this example, we will raise the event directly from the master page.
<%@ Master Language="VB" %>
<script runat="server">
End Sub
</script>
We'll need to add some validation logic to the master page, but at this point all we need is to
handle the event in our page. We could also handle the event from a base page class, if we don’t
want to duplicate this code for every page.
<%@ Page Language="VB" MasterPageFile="~/Master1.master"
AutoEventWireup="true" %>
<%@ MasterType VirtualPath="~/Master1.master" %>
<script runat="server">
' do work
End Sub
</script>
When the user click the search button, the web request will ultimately arrive at the
SearchResults.aspx. How will SearchResults.aspx find the text the user wants to search for? We
could use the PreviousPage.Master property and FindControl to locate the QueryBox TextBox by
its ID, but we’ve already discussed some reasons to avoid FindControl when possible.
What about the exposing the text as a property? It sounds easy, but...
In ASP.NET 2.0, each master page and web form can compile into a separate assembly. Unless
we establish a reference between two assemblies, the types inside each assembly cannot see one
another. The @ MasterType directive with a VirtualPath attribute ensures the web form’s
assembly will reference the master page assembly. If our SearchResults.aspx page uses the same
@ MasterType directive as the POSTing web form, it will be able to see the master page type,
and life is simple.
Let’s assume our SearchResults.aspx page does not use a master page, and we don’t want to use
FindControl. Inheritance is once again a solution to this problem. We will need a base class (or
an interface) defined in App_Code or a class library (all web form and master page assemblies
reference the App_Code assembly). Here is a base class solution.
public class BaseMasterPage : MasterPage
{
protected Label PageFooter;
protected TextBox QueryBox;
// ...
' do search
End If
While the above approach works pretty, well, you might consider going a step further. Define an
interface with a QueryText property and derive a base page (not master page) class from the
interface. The base page class can go to the trouble of getting the text from the master page.
Now, SearchResults.aspx doesn’t have to worry about master pages at all. It can use a cast to get
a reference to the interface from the PreviousPage reference, and then ask the interface for the
QueryText. Any type of page can then post to SearchResults, even those without a master page.
If we want to add script or meta tags from a content page, we have more work to do. Here is an
example of injecting a redirection meta tag:
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs)
End Sub
The Page class contains a public property named Header. Header gives us access to the head tag
as a server side control (the head tag in the master page must include runat=”server” for the
Header property to work). We can add style sheets to the header tag, too.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs)
End Sub
We can also add markup inside the head tag using an HtmlGenericControl, which provides
TagName, InnerText, InnerHtml, and Attributes properties.
Header Place Holders
There is another approach we can use to modify the header, which does have one drawback. The
ContentPlaceHolder and Content controls will merge even when we place a ContentPlaceHolder
control outside of the <form> tag. Take the following master page excerpt as an example.
<head runat="server">
<title>Untitled Page</title>
<asp:ContentPlaceHolder id="headerPlaceHolder" runat="server" />
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
<asp:Label runat="server" ID="PageFooter" Text="Default footer text" />
</form>
</body>
</html>
This master page uses a ContentPlaceHolder inside the head tag. Remember, a Content page isn’t
required to provide a Content control for every ContentPlaceHolder control in a master page. If
there is no Content control available for the master to merge into a ContentPlaceHolder, the
master page uses the default content inside of the ContentPlaceHolder. In the above code, we did
not specify any default content, but this is a trick to remember if you want to provide default
content with the ability to replace the default content from any given content page.
With the ContentPlaceHolder above, any content page can add additional tags inside the head tag
using a Content control.
<asp:Content ID="HeaderContent" runat="server"
ContentPlaceHolderID="headerPlaceHolder">
<link rel="stylesheet" type="text/css" href="customstyles.css" />
</asp:Content>
To use the MetaKeywords attribute in every page of an application, we just need to inherit from a
common base class that exposes a MetaKeywords property. The base class can also inject the
meta tag into the page header.
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
Page.FindControl("Label1").Visible = False
End Sub
</script>
FindControl in the above code returns a null (Nothing) reference. Why? Let’s turn to the
FindControl documentation on MSDN.
FindControl searches the current naming container for the specified server control.
A naming container is any control that carries the INamingContainer interface. Both the
MasterPage and Content controls are naming containers. The key to using FindControl is to
invoke the method on the correct container, because FindControl doesn’t recursively traverse the
entire hierarchy of controls. FindControl only searches inside the current naming container.
Using the FindControl method on the Page reference means we won’t be searching inside of
MasterPage control. course, we don’t need to use FindControl in this scenario because our
content page will have a Label1 field, but if you do need to use FindControl for a control in a
content page, the following code will be helpful.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim content As ContentPlaceHolder
content = Page.Master.FindControl("ContentPlaceHolder1")
End Sub
First, our code has to find the ContentPlaceHolder containing the Label control. We will use the
MasterPage control's FindCotnrol method. The MasterPage inside of our page is the naming
container that contains ContentPlaceHolder1. If you are wondering why we are not using the
Content1 control, it’s because no Content controls exist. Remember our early discussion on how
master pages work. Master pages copy the controls inside of the Content controls into
ContentPlaceHolder controls. The Content controls get left behind and don’t exist in the control
hierarchy.
Once we have a reference to the ContentPlaceHolder control, we use FindControl a second time
to locate the Label control. We could shorten all the above code into a single line:
Master.FindControl(...).FindControl(..).Visible = False
Name Mangling
A naming container also mangles its children’s ClientID property. Mangling ensures all ClientID
properties are unique on a page. For instance, the ID for our Label control is “Label1”, but the
ClientID of the Label is “ctl00_ContentPlaceHolder1_Label1”. Each level of naming container
prepends it’s ID to the control (the MasterPage control ID in this form is ctl00). Just as we have
to be careful with FindControl, we have to be careful with client side script functions like
getElementById. If we emit the following script into our page, it will fail with a JavaScript error:
‘Label1 is undefined’.
<script type="text/javascript">
<!--
Label1.innerHTML = 'Hello, from script!';
// -->
</script>
Of course, we’d never want to hardcode the client ID into a script. Typically we’ll need to build
the script dynamically using StringBuilder or String.Format. Another alternative is to use
markers in the script and use a call to String.Replace, like the following.
Dim script As String = "[Label1ID].innerHTML = 'boo!';"
Dim scriptKey As String = "SayBoo"
Dim addScriptTags As Boolean = True
End Sub
As long as the master page and the web form live in the same directory, the company logo will
display in the browser. When the master page and web form live in different directories, the
image will not appear. The browser requests knows nothing about master pages. The browser
will interpret any relative paths it finds in the HTML as being relative to the webform. If our
logo and master page files are in the root directory, but the web form is in a subdirectory, the
browser will ask for logo.gif from the same subdirectory. The server will respond with a 404 (file
not found) error.
The good news is, the ASP.NET runtime does provide a feature called “URL rebasing”. The
runtime will try to “rebase” relative URLs it finds on server-side controls inside a master page.
This means the following relative path will work, no matter where the master page and web form
live.
<img src="logo.gif" alt="Company Logo" runat="server" />
We’ve added a runat=”server” attribute to the image tag, making the <img> a server-side control.
When the master page file and logo are in the root directory, but the web form is in a
subdirectory, the ASP.NET runtime will rebase the relative path it finds in the src attribute to
point to the root of the website.
The following code will also work, because we are using a server-side Image object.
<asp:Image ImageUrl="logo.gif" runat="server" />
The ASP.NET runtime will also rebase paths it finds inside of the head tag. Take the following
excerpt from a master page:
<head runat="server">
<title>Untitled Page</title>
If we request a webform from a subdirectory, the runtime will catch the href inside the link tag
and rebase the URL to "../styles/styles.css". However, the runtime doesn’t catch everything. If we
included our style sheet with the following code, the runtime won’t rebase the relative href.
<head runat="server">
</head>
Also, the runtime doesn’t rebase URLs inside of embedded styles, and not all attributes are
covered (the background attribute, for instance).
<body background="logo.gif" runat="server">
<!-- the background for the body tag will break -->
<form id="form1" runat="server">
If you need to use a relative path in an area where the runtime does not provide the rebasing
feature, you can compute a client side URL using ResolveClientUrl and passing a relative path.
ResolveClientUrl, when called from inside a master page, will take into account the location of
the master page, the location specified in the HTTP request, and the location specified by the
relative path parameter to formulate the correct relative path to return.
<body background=<%= ResolveClientUrl("logo.gif") %> >
When working with image paths in embedded styles, it’s often a good idea to move the style
definition into a .css file. The ASP.NET runtime will rebase the path it finds inside a link tag, so
we won’t have any problems locating the stylesheet from any webform. Take the following style
definition in a .css file:
body
{
background-image:url('images\logo.gif');
}
Relative paths are safe inside a .css file because the browser will always request logo.gif relative
to the location of the stylesheet.
Notice the skin uses a relative path, so we can have a different logo graphic underneath each
theme we define. ASP.NET will rebase the path to the gif file. The master page only needs to use
the following markup.
<asp:Image ID="Image1" runat="server" SkinID="logo" />
Different logos can exist theme, and the skin we defined will only apply to Image controls with a
SkinID of “logo”.
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
Then, we create a second master page (Nested.master) that uses master1.master as a master page.
<%@ Master Language="VB" MasterPageFile="~/Master1.master" %>
<h3>Nested Content</h3>
<asp:contentplaceholder id="NestedContent" runat="server">
</asp:contentplaceholder>
</asp:Content>
If we attempt to view this content page in design view, Visual Studio will produce the error
message shown earlier. If we really want to use the designer with our content page, we can leave
the MasterPageFile attribute empty, like in the following code:
<%@ Page Language="VB" MasterPageFile="" %>
<asp:Content ID="Content1" ContentPlaceHolderID="NestedContent" Runat="Server">
</asp:Content>
We can’t just drop the MasterPageFile attribute from the @ Page directive, because the designer
will raise a different error (“Content controls are allowed only in content page that references a
master page”). The empty attribute appears to trick the designer into allowing us into design
mode.
At runtime, however, the page will throw an exception because it doesn’t have a master file. We
can avoid the exception by programmatically setting the MasterPageFile property at runtime. We
know we will need to set the master page before or during the PreInit event. The following code
reads the masterPageFile attribute from the <pages> section of web.config. By putting the code
into a base class, we can cover all the content pages in an application.
using System;
using System.Web.UI;
using System.Web.Configuration;
using System.Configuration;
PagesSection pagesConfig =
ConfigurationManager.GetSection("system.web/pages")
as PagesSection;
MasterPageFile = pagesConfig.MasterPageFile;
}
}
This topic supports using the Minimal Master Page described as a Site Master Page in Office
SharePoint Server 2007. It does not support using the Minimal Master Page described in this
topic as a System Master Page in Office SharePoint Server 2007. Using this content with
Windows SharePoint Services 3.0 is not explicitly supported.
You can, of course, create a master page from scratch. However, we generally do not recommend
this because a truly empty master page does not include all the content placeholders that the
Office SharePoint Server 2007 page model needs to work correctly.
The sample code in the following procedure includes only what the Office SharePoint Server
2007 page model requires—necessary content placeholders and controls to work with the page
layouts that are included in a default Office SharePoint Server 2007 installation. Office
SharePoint Server 2007 requires a master page that includes a title, branding, logon functionality,
search functionality, breadcrumb functionality, and basic structural elements such as page areas,
separators, borders, consoles, and description placeholders.
The following procedure uses Office SharePoint Designer 2007 as the master page design
environment. You can, however, use a text editor, a Web editor such as Microsoft Office
SharePoint Designer 2007, or an integrated development environment (IDE) such as Microsoft
Visual Studio 2005 to create a master page.
The master pages included with Office SharePoint Server 2007 are based on the
SPWeb.CustomMasterUrl property of the SPWeb class in Windows SharePoint Services.
Copy Code
<%-- Identifies this page as a .master page written in Microsoft Visual
C# and registers tag prefixes, namespaces, assemblies, and controls. --
%>
<%@ Master language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register Tagprefix="SPSWC"
Namespace="Microsoft.SharePoint.Portal.WebControls"
Assembly="Microsoft.SharePoint.Portal, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint"
Namespace="Microsoft.SharePoint.WebControls"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages"
Namespace="Microsoft.SharePoint.WebPartPages"
Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="PublishingWebControls"
Namespace="Microsoft.SharePoint.Publishing.WebControls"
Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="PublishingNavigation"
Namespace="Microsoft.SharePoint.Publishing.Navigation"
Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="Welcome"
src="~/_controltemplates/Welcome.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="DesignModeConsole"
src="~/_controltemplates/DesignModeConsole.ascx" %>
<%@ Register TagPrefix="PublishingVariations"
TagName="VariationsLabelMenu"
src="~/_controltemplates/VariationsLabelMenu.ascx" %>
<%@ Register Tagprefix="PublishingConsole" TagName="Console"
src="~/_controltemplates/PublishingConsole.ascx" %>
<%@ Register TagPrefix="PublishingSiteAction" TagName="SiteActionMenu"
src="~/_controltemplates/PublishingActionMenu.ascx" %>
<%-- Uses the Microsoft Office namespace and schema. --%>
<html>
<WebPartPages:SPWebPartManager runat="server"/>
<SharePoint:RobotsMetaTag runat="server"/>
<%-- The head section includes a content placeholder for the page
title and links to CSS and ECMAScript (JScript, JavaScript) files that
run on the server. --%>
<head runat="server">
<asp:ContentPlaceHolder runat="server" id="head">
<title>
<asp:ContentPlaceHolder id="PlaceHolderPageTitle"
runat="server" />
</title>
</asp:ContentPlaceHolder>
<Sharepoint:CssLink runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead"
runat="server" />
</head>
<%-- When loading the body of the .master page, SharePoint Server
2007 also loads the SpBodyOnLoadWrapper class. This class handles .js
calls for the master page. --%>
<body onload="javascript:_spBodyOnLoadWrapper();">
<%-- The SPWebPartManager manages all of the Web part controls,
functionality, and events that occur on a Web page. --%>
<form runat="server" onsubmit="return _spFormOnSubmitWrapper();">
<wssuc:Welcome id="explitLogout" runat="server"/>
<PublishingSiteAction:SiteActionMenu runat="server"/>
<PublishingWebControls:AuthoringContainer id="authoringcontrols"
runat="server">
<PublishingConsole:Console runat="server" />
</PublishingWebControls:AuthoringContainer>
<%-- The PlaceHolderMain content placeholder defines where to
place the page content for all the content from the page layout. The
page layout can overwrite any content placeholder from the master page.
Example: The PlaceHolderLeftNavBar can overwrite the left navigation
bar. --%>
<asp:ContentPlaceHolder id="PlaceHolderMain" runat="server" />
<asp:Panel visible="false" runat="server">
<%-- These ContentPlaceHolders ensure all default SharePoint
Server pages render with this master page. If the system master page is
set to any default master page, the only content placeholders required
are those that are overridden by your page layouts. --%>
<asp:ContentPlaceHolder id="PlaceHolderSearchArea" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderTitleBreadcrumb" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderPageTitleInTitleArea"
runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderLeftNavBar" runat="server"/>
<asp:ContentPlaceHolder ID="PlaceHolderPageImage" runat="server"/>
<asp:ContentPlaceHolder ID="PlaceHolderBodyLeftBorder" runat="server"/>
<asp:ContentPlaceHolder ID="PlaceHolderNavSpacer" runat="server"/>
<asp:ContentPlaceHolder ID="PlaceHolderTitleLeftBorder" runat="server"/>
<asp:ContentPlaceHolder ID="PlaceHolderTitleAreaSeparator"
runat="server"/>
<asp:ContentPlaceHolder ID="PlaceHolderMiniConsole" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderCalendarNavigator" runat
="server" />
<asp:ContentPlaceHolder id="PlaceHolderLeftActions" runat ="server"/>
<asp:ContentPlaceHolder id="PlaceHolderPageDescription" runat
="server"/>
<asp:ContentPlaceHolder id="PlaceHolderBodyAreaClass" runat ="server"/>
<asp:ContentPlaceHolder id="PlaceHolderTitleAreaClass" runat ="server"/>
<asp:ContentPlaceHolder id="PlaceHolderBodyRightMargin" runat="server"
/>
</asp:Panel>
</form>
</body>
</html>
7. On the File menu, click Save As, provide a unique file name with the .master extension,
and then save the file to the master page gallery (/_catalogs/masterpage) in your site
collection.