Specifying the Title, Meta Tags, and Other HTML Headers in the Master Page (C#)
Part 3
Introduction
New master pages created in Visual Studio 2008 have, by default, two ContentPlaceHolder controls: one named head, and located in the<head>
element; and one named ContentPlaceHolder1
, placed within the Web Form. The purpose of ContentPlaceHolder1
is to define a region in the Web Form that can be customized on a page-by-page basis. The head
ContentPlaceHolder enables pages to add custom content to the <head>
section. (Of course, these two ContentPlaceHolders can be modified or
removed, and additional ContentPlaceHolder may be added to the master
page. Our master page, Site.master
, currently has four ContentPlaceHolder controls.)The HTML
<head>
element serves as a repository for
information about the web page document that is not part of the
document itself. This includes information such as the web page's title,
meta-information used by search engines or internal crawlers, and links
to external resources, such as RSS feeds, JavaScript, and CSS files.
Some of this information may be pertinent to all pages in the website.
For example, you might want to globally import the same CSS rules and
JavaScript files for every ASP.NET page. However, there are portions of
the <head>
element that are page-specific. The page title is a prime example.In this tutorial we examine how to define global and page-specific
<head>
section markup in the master page and in its content pages.
Examining the Master Page's <head>
Section
The default master page file created by Visual Studio 2008 contains the following markup in its <head>
section:<head runat="server">
<title>Untitled Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
Notice that the <head>
element contains a runat="server"
attribute, which indicates that it is a server control (rather than static HTML). All ASP.NET pages derive from the Page
class, which is located in the System.Web.UI
namespace. This class contains a Header
property that provides access to the page's <head>
region. Using the Header
property we can set an ASP.NET page's title or add additional markup to the rendered <head>
section. It is possible, then, to customize a content page's <head>
element by writing a bit of code in the page's Page_Load
event handler. We examine how to programmatically set the page's title in Step 1.The markup shown in the
<head>
element above also
includes a ContentPlaceHolder control named head. This
ContentPlaceHolder control is not necessary, as content pages can add
custom content to the <head>
<head>
element as the static markup can be added declaratively to the corresponding Content control rather than programmatically. element programmatically. It's useful, however, in situations where a content page needs to add static markup to the In addition to the
<title>
element and head ContentPlaceHolder, the master page's <head>
element should contain any <head>
-level markup that's common to all pages. In our website, all pages use the CSS rules defined in the Styles.css
file. Consequently, we updated the <head>
element in the Creating a Site-Wide Layout with Master Pages tutorial to include a corresponding <link>
element. Our Site.master
master page's current <head>
markup is shown below.<head runat="server">
<title>Untitled Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
Step 1: Setting a Content Page's Title
The web page's title is specified via the<title>
element. It is important to set each page's title to an appropriate
value. When visiting a page, its title is displayed in the browser's
Title bar. Additionally, when bookmarking a page, browsers use the
page's title as the suggested name for the bookmark. Also, many search
engines show the page's title when displaying search results.
Note: By default, Visual Studio sets the
An ASP.NET page can specify its title in one of the following ways:<title>
element in the master page to "Untitled Page". Similarly, new ASP.NET pages have their <title>
set to "Untitled Page", too. Because it can be easy to forget to set
the page's title to an appropriate value, there are many pages on the
Internet with the title "Untitled Page". Searching Google for web pages
with this title returns roughly 2,460,000 results. Even Microsoft is
susceptible to publishing web pages with the title "Untitled Page". At
the time of this writing, a Google search reported 236 such web pages in
the Microsoft.com domain.- By placing the value directly within the
<title>
element - Using the
Title
attribute in the<%@ Page %>
directive - Programmatically setting the page's
Title
property using code likePage.Title="title"
orPage.Header.Title="title"
.
<title>
element, as it's defined in the master page. Therefore, to set a content page's title you can either use the <%@ Page %>
directive's Title
attribute or set it programmatically.Setting the Page's Title Declaratively
A content page's title can be set declaratively through theTitle
attribute of the <%@ Page %>
directive. This property can be set by directly modifying the <%@ Page %>
directive or through the Properties window. Let's look at both approaches.From the Source view, locate the
<%@ Page %>
directive, which is at the top of the page's declarative markup. The <%@ Page %>
directive for Default.aspx
follows:<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %>
The <%@ Page %>
directive specifies page-specific
attributes used by the ASP.NET engine when parsing and compiling the
page. This includes its master page file, the location of its code file,
and its title, among other information.By default, when creating a new content page Visual Studio sets the
Title
attribute to Untitled Page. Change Default.aspx
's Title
attribute from "Untitled Page" to "Master Page Tutorials" and then view
the page through a browser. Figure 1 shows the browser's Title bar,
which reflects the new page title.Title
Title
has been set to "Master Page Tutorials". property. Figure 2 shows the Properties window after Setting the Page's Title Programmatically
The master page's<head runat="server">
markup is translated into an HtmlHead
class instance when the page is rendered by the ASP.NET engine. The HtmlHead
class has a Title
property whose value is reflected in the rendered <title>
element. This property is accessible from an ASP.NET page's code-behind class via Page.Header.Title
; this same property can also be accessed via Page.Title
.To practice setting the page's title programmatically, navigate to the
About.aspx
page's code-behind class and create an event handler for the page's Load
event. Next, set the page's title to "Master Page Tutorials :: About :: date", where date is the current date. After adding this code your Page_Load
event handler should look similar to the following:protected void Page_Load(object sender, EventArgs e)
{
Page.Title = string.Format("Master Page Tutorials :: About :: {0:d}", DateTime.Now);
}
Figure 3 shows the browser's title bar when visiting the About.aspx
page.Step 2: Automatically Assigning a Page Title
As we saw in Step 1, a page's title can be set declaratively or programmatically. If you forget to explicitly change the title to something more descriptive, however, your page will have the default title, "Untitled Page". Ideally, the page's title would be set automatically for us in the event that we don't explicitly specify its value. For example, if at runtime the page's title is "Untitled Page", we might want to have the title automatically updated to be the same as the ASP.NET page's filename. The good news is that with a little bit of upfront work it is possible to have the title automatically assigned.All ASP.NET web pages derive from the
Page
class in the System.Web.UI
namespace. The Page
class defines the minimal functionality needed by an ASP.NET page and exposes essential properties like IsPostBack
, IsValid
, Request
, and Response
,
among many others. Oftentimes, every page in a web application requires
additional features or functionality. A common way of providing this is
to create a custom base page class. A custom base page class is a class
you create that derives from the Page
class and includes
additional functionality. Once this base class has been created, you can
have your ASP.NET pages derive from it (rather than the Page
class), thereby offering the extended functionality to your ASP.NET pages.In this step we create a base page that automatically sets the page's title to the ASP.NET page's filename if the title has not otherwise been explicitly set. Step 3 looks at setting the page's title based on the site map.
Note: A thorough examination of
creating and using custom base page classes is beyond the scope of this
tutorial series. For more information, read Using a Custom Base Class for Your ASP.NET Pages' Code-Behind Classes.
Creating the Base Page Class
Our first task is to create a base page class, which is a class that extends thePage
class. Start by adding an App_Code
folder to your project by right-clicking on the project name in the
Solution Explorer, choosing Add ASP.NET Folder, and then selecting App_Code
. Next, right-click on the App_Code
folder and add a new class named BasePage.cs
. Figure 4 shows the Solution Explorer after the App_Code
folder and BasePage.cs
class have been added.
Note: Visual Studio supports two modes of project management: Web Site Projects and Web Application Projects. The
Because the custom base page serves as the base class for ASP.NET pages' code-behind classes, it needs to extend the App_Code
folder is designed to be used with the Web Site Project model. If you are using the Web Application Project model, place the BasePage.cs
class in a folder named something other than App_Code
, such as Classes
. For more information on this topic, refer to Migrating a Web Site Project to a Web Application Project.Page
class.public class BasePage : System.Web.UI.Page
{
}
Whenever an ASP.NET page is requested it proceeds through a series of
stages, culminating in the requested page being rendered into HTML. We
can tap into a stage by overriding the Page
class's OnEvent
method. For our base page let's automatically set the title if it has not been explicitly specified by the LoadComplete
stage (which, as you might have guessed, occurs after the Load
stage).To accomplish this, override the
OnLoadComplete
method and enter the following code:protected override void OnLoadComplete(EventArgs e)
{
// Set the page's title, if necessary
if (string.IsNullOrEmpty(Page.Title) || Page.Title == "Untitled Page")
{
// Determine the filename for this page
string fileName = System.IO.Path.GetFileNameWithoutExtension(Request.PhysicalPath);
Page.Title = fileName;
}
base.OnLoadComplete(e);
}
The OnLoadComplete
method starts by determining if the Title
property has not yet been explicitly set. If the Title
property is null
,
an empty string, or has the value "Untitled Page", it is assigned to
the filename of the requested ASP.NET page. The physical path to the
requested ASP.NET page - C:\MySites\Tutorial03\Login.aspx
, for example - is accessible via the Request.PhysicalPath
property. The Path.GetFileNameWithoutExtension
method is used to pull out just the filename portion, and this filename is then assigned to the Page.Title
property.
Note: I invite you to enhance this logic to improve the format of the title. For example, if the page's filename is
Company-Products.aspx
,
the above code will produce the title "Company-Products", but ideally
the dash would be replaced with a space, as in "Company Products". Also,
consider adding a space whenever there's a case change. That is,
consider adding code that transforms the filename OurBusinessHours.aspx
to a title of "Our Business Hours".Having the Content Pages Inherit the Base Page Class
We now need to update the ASP.NET pages in our site to derive from the custom base page (BasePage
) instead of the Page
class. To accomplish this go to each code-behind class and change the class declaration from:public partial class ClassName : System.Web.UI.Page
To:public partial class ClassName : BasePage
After doing so, visit the site through a browser. If you visit a page whose title is explicitly set, such as Default.aspx
or About.aspx
,
the explicitly specified title is used. If, however, you visit a page
whose title has not been changed from the default ("Untitled Page"), the
base page class sets the title to the page's filename. Figure 5 shows the
MultipleContentPlaceHolders.aspx
page
when viewed through a browser. Note that the title is precisely the
page's filename (less the extension), "MultipleContentPlaceHolders".Step 3: Basing the Page Title on the Site Map
ASP.NET offers a robust site map framework that enables page developers to define a hierarchical site map in an external resource (such as an XML file or database table) along with Web controls for displaying information about the site map (such as the SiteMapPath, Menu, and TreeView controls).The site map structure can also be accessed programmatically from an ASP.NET page's code-behind class. In this manner we can automatically set a page's title to the title of its corresponding node in the site map. Let's enhance the
BasePage
class created in Step 2 so that it offers this functionality. But first we need to create a site map for our site.
Note: This tutorial assumes the
reader already is familiar with ASP.NET's site map features. For more
information on using the site map, consult my multi-part article series,
Examining ASP.NET's Site Navigation.
Creating the Site Map
The site map system is built atop the provider model, which decouples the site map API from the logic that serializes site map information between memory and a persistent store. The .NET Framework ships with theXmlSiteMapProvider
class, which is the default site map provider. As its name implies, XmlSiteMapProvider
uses an XML file as its site map store. Let's use this provider for defining our site map.Start by creating a site map file in the website's root folder named
Web.sitemap
.
To accomplish this, right-click on the website name in Solution
Explorer, choose Add New Item, and select the Site Map template. Ensure
that the file is named Web.sitemap
and click Add.Add the following XML to the
Web.sitemap
file:<?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>
</siteMap>
This XML defines the hierarchical site map structure shown in Figure 7.Updating the Master Page to Include Navigation Web Controls
Now that we have a site map defined, let's update the master page to include navigation Web controls. Specifically, let's add a ListView control to the left column in the Lessons section that renders an unordered list with a list item for each node defined in the site map.
Note: The ListView control is new
to ASP.NET version 3.5. If you are using a prior version of ASP.NET,
use the Repeater control instead. For more information on the ListView
control, see Using ASP.NET 3.5's ListView and DataPager Controls.
Start by removing the existing unordered list markup from the Lessons
section. Next, drag a ListView control from the Toolbox and drop it
beneath the Lessons heading. The ListView is located in the Data section
of the Toolbox, alongside the other view controls: the GridView,
DetailsView, and FormView. Set the ListView's ID property to LessonsList
.From the Data Source Configuration Wizard choose to bind the ListView to a new SiteMapDataSource control named
LessonsDataSource
. The SiteMapDataSource control returns the hierarchical structure from the site map system.After creating the SiteMapDataSource control, we need to define the ListView's templates so that it renders an unordered list with a list item for each node returned by the SiteMapDataSource control. This can be accomplished using the following template markup:
<asp:ListView ID="LessonsList" runat="server" DataSourceID="LessonsDataSource">
<LayoutTemplate>
<ul>
<asp:PlaceHolder runat="server" ID="itemPlaceholder" />
</ul>
</LayoutTemplate>
<ItemTemplate>
<li><asp:HyperLink runat="server" ID="lnkLesson" NavigateUrl='<%# Eval("Url") %>'
Text='<%# Eval("Title") %>' /></li>
</ItemTemplate>
</asp:ListView>
The LayoutTemplate
generates the markup for an unordered list (<ul>...</ul>
) while the ItemTemplate
renders each item returned by the SiteMapDataSource as a list item (<li>
) that contains a link to the particular lesson.After configuring the ListView's templates, visit the website. As Figure 9 shows, the Lessons section contains a single bulleted item, Home. Where are the About and Using Multiple ContentPlaceHolder Controls lessons? The SiteMapDataSource is designed to return a hierarchical set of data, but the ListView control can only display a single level of the hierarchy. Consequently, only the first level of site map nodes returned by the SiteMapDataSource is displayed.
To display multiple levels we could nest multiple ListViews within the
ItemTemplate
. This technique was examined in the Master Pages and Site Navigation tutorial of my Working with Data tutorial series.
However, for this tutorial series our site map will contain just a two
levels: Home (the top level); and each lesson as a child of Home. Rather
than crafting a nested ListView, we can instead instruct the
SiteMapDataSource to not return the starting node by setting its ShowStartingNode
property to false
. The net effect is that the SiteMapDataSource starts by returning the second tier of site map nodes.With this change, the ListView displays bullet items for the About and Using Multiple ContentPlaceHolder Controls lessons, but omits a bullet item for Home. To remedy this, we can explicitly add a bullet item for Home in the
LayoutTemplate
:<LayoutTemplate>
<ul>
<li><asp:HyperLink runat="server" ID="lnkLesson"
NavigateUrl="~/Default.aspx" Text="Home" /></li>
<asp:PlaceHolder runat="server" ID="itemPlaceholder" />
</ul>
</LayoutTemplate>
By configuring the SiteMapDataSource to omit the starting node and
explicitly adding a Home bullet item, the Lessons section now displays
the intended output.Setting the Title Based on the Site Map
With the site map in place, we can update ourBasePage
class to use the title specified in the site map. As we did in Step 2,
we only want to use the site map node's title if the page's title has
not been explicitly set by the page developer. If the page being
requested does not have an explicitly set page title and is not found in
the site map, then we'll fall back to using the requested page's
filename (less the extension), as we did in Step 2. Figure 11
illustrates this decision process.BasePage
class's OnLoadComplete
method to include the following code:protected override void OnLoadComplete(EventArgs e)
{
// Set the page's title, if necessary
if (string.IsNullOrEmpty(Page.Title) || Page.Title == "Untitled Page")
{
// Is this page defined in the site map?
string newTitle = null;
SiteMapNode current = SiteMap.CurrentNode;
if (current != null)
{
newTitle = current.Title;
}
else
{
// Determine the filename for this page
newTitle = System.IO.Path.GetFileNameWithoutExtension(Request.PhysicalPath);
}
Page.Title = newTitle;
}
base.OnLoadComplete(e);
}
As before, the OnLoadComplete
method starts by determining whether the page's title has been explicitly set. If Page.Title
is null
, an empty string, or is assigned the value "Untitled Page" then the code automatically assigns a value to Page.Title
. To determine the title to use, the code starts by referencing the
SiteMap
class's CurrentNode
property. CurrentNode
returns the SiteMapNode
instance in the site map that corresponds to the currently requested
page. Assuming the currently requested page is found within the site
map, the SiteMapNode
's Title
CurrentNode
null
and the requested page's filename is used as the title (as was done in Step 2). property is assigned to the page's title. If the currently requested page is not in the site map, returns Figure 12 shows the
MultipleContentPlaceHolders.aspx
page when viewed through a browser. Because this page's title is not
explicitly set, its corresponding site map node's title is used instead.
Step 4: Adding Other Page-Specific Markup to the <head>
Section
Steps 1, 2, and 3 looked at customizing the <title>
element on a page-by-page basis. In addition to <title>
, the <head>
section may contain <meta>
elements and <link>
elements. As noted earlier in this tutorial, Site.master
's <head>
section includes a <link>
element to Styles.css
. Because this <link>
<head>
section for all content pages. But how do we go about adding <meta>
and <link>
elements on a page-by-page basis? element is defined within the master page, it is included in the The easiest way to add page-specific content to the
<head>
section is by creating a ContentPlaceHolder control in the master page. We already have such a ContentPlaceHolder (named head
). Therefore, to add custom <head>
markup, create a corresponding Content control in the page and place the markup there.To illustrate adding custom
<head>
markup to a page, let's include a <meta>
description element to our current set of content pages. The <meta>
description element provides a brief description about the web page;
most search engines incorporate this information in some form when
displaying search results.A
<meta>
description element has the following form:<meta name="description" content="description of the web page" />
To add this markup to a content page, add the above text to the
Content control that maps to the master page's head ContentPlaceHolder.
For example, to define a <meta>
description element for Default.aspx
, add the following markup:<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
<meta name="description" content="Welcome to Scott Mitchell's Master Page Tutorials series." />
</asp:Content>
Because the head ContentPlaceHolder is not within the HTML page's
body, the markup added to the Content control is not displayed in the
Design view. To see the <meta>
description element visit Default.aspx
through a browser. After the page has been loaded, view the source and note that the <head>
section includes the markup specified in the Content control.Take a moment to add
<meta>
description elements to About.aspx
, MultipleContentPlaceHolders.aspx
, and Login.aspx
.
Programmatically Adding Markup to the <head>
Region
The head ContentPlaceHolder allows us to declaratively add custom markup to the master page's <head>
Page
class's Header
property returns the HtmlHead
instance defined in the master page (the <head runat="server">
). region. Custom markup may also be added programmatically. Recall that the Being able to programmatically add content to the
<head>
region is useful when the content to add is dynamic. Perhaps it's based
on the user visiting the page; maybe it's being pulled from a database.
Regardless of the reason, you can add content to the HtmlHead
by adding controls to its Controls collection like so:// Programmatically add a <meta> element to the Header
HtmlMeta keywords = new HtmlMeta();
keywords.Name = "keywords";
keywords.Content = "master page,asp.net,tutorial";
Page.Header.Controls.Add(keywords);
The above code adds the <meta>
keywords element to the <head>
region, which provides a comma-delimited list of keywords that describe the page. Note that to add a <meta>
tag you create an HtmlMeta
Name
and Content
properties, and then add it to the Header
's Controls
collection. Similarly, to programmatically add a <link>
element, create an HtmlLink
object, set its properties, and then add it to the Header
's Controls
collection. instance, set its
Note: To add arbitrary markup, create a
LiteralControl
instance, set its Text
property, and then add it to the Header
's Controls
collection.
No comments :
Post a Comment