WHAT’S IN THIS CHAPTER?
WROX.COM CODE DOWNLOADS FOR THIS CHAPTER
Please note that all the code examples in this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com on the Download Code tab.
Visual inheritance is a great feature provided in ASP.NET that you can use to build your web pages. This feature was first introduced to ASP.NET in version 2.0. In effect, you can create a single template page that you can use as a foundation for any number of ASP.NET content pages in your application. These templates, called master pages, increase your productivity by making your applications easier to build and easier to manage after they are built. Visual Studio 2012 includes full designer support for master pages, making the developer experience richer than ever before. For example, even if you have nested master pages, Visual Studio provides you with a quite realistic WYSIWYG view of how the page might actually look in the browser.
This chapter takes a close look at how to utilize master pages to the fullest extent in your applications, and begins by explaining the advantages of master pages.
Most websites today have common elements that are used throughout the entire application or on a majority of the pages within the application. For example, if you look at the main page of the Reuters News website (www.reuters.com), you see common elements that are used throughout the entire website. These common areas are labeled in Figure 16-1.
In this screenshot, notice the header section, the navigation section, and the footer section on the page. In fact, nearly every page within the entire site uses these same elements. Even before master pages, you had ways to put these elements into every page through a variety of means; but in most cases, doing so posed difficulties.
Some developers simply copy and paste the code for these common sections to each and every page that requires them. This works, but it’s rather labor intensive — whenever you need to make a change to one of these common sections, you have to go into each and every page and duplicate the change. That’s not much fun and an ineffective use of your time!
In the days of Classic Active Server Pages, one popular option was to put all the common sections into what was called an include file. You could then place this file within your page like this:
<!-- #include virtual="/myIncludes/header.asp" -->
The problem with using include files was that you had to take into account the newly opened HTML tags in the header include file. These tags had to be closed in the main document or in the footer include file. Keeping all the HTML tags in order was usually difficult, especially if multiple people worked on a project. Web pages sometimes displayed strange results because of inappropriate or nonexistent tag closings or openings. Working with include files in a visual designer was also difficult. Using include files didn’t allow the developer to see the entire page as it would appear in a browser. The developer ended up developing the page in sections and hoping that the pieces would come together as planned. Many hours were wasted “chasing tables” opened in an include file and possibly closed later!
With the introduction of ASP.NET 1.0 in 2000, developers started using user controls to encapsulate common sections of their web pages. For example, you could build a web page that included header, navigation, and footer sections by simply dragging and dropping these sections of code onto each page that required them.
This technique worked, but it also raised some issues. Before Visual Studio 2005 and ASP.NET 2.0, user controls caused problems similar to those related to include files. When you worked in the Design view of your web page, the common areas of the page displayed only as gray boxes in Visual Studio .NET 2002 and 2003. This made building a page harder: You could not visualize what the page you were building actually looked like until you compiled and ran the completed page in a browser. User controls also suffered from the same problem as include files — you had to match up the opening and closing of your HTML tags in two separate files. Personally, I prefer user controls over include files, but user controls aren’t perfect template pieces for use throughout an application. You will find that Visual Studio corrects some of the problems by rendering user-control content in the Design view. User controls are ideal if you are including only small sections on a web page; they are still rather cumbersome, however, when working with larger page templates.
In light of the issues with include files and user controls, the ASP.NET team developed the idea of master pages — an outstanding way of applying templates to your applications. They inverted the way the developer attacks the problem. Master pages live outside the pages you develop, whereas user controls live within your pages and are doomed to duplication. These master pages draw a more distinct line between the common areas that you carry over from page to page and the content areas that are unique on each page. You will find that working with master pages is easy and fun. The next section of this chapter looks at some of the basics of master pages in ASP.NET.
Master pages are an easy way to provide a template that can be used by any number of ASP.NET pages in your application. In working with master pages, you create a master file that is the template referenced by a subpage or content page. Master pages use a .master file extension, whereas content pages use the .aspx file extension you’re used to; but content pages are declared as such within the file’s Page directive.
You can place anything you want to include as part of the template in the .master file, such as the header, navigation, and footer sections used across the web application. The content page itself would then only contain all the page-specific content but not the master page’s elements. At run time, the ASP.NET engine combines these elements into a single page for the end user. Figure 16-2 shows a diagram of how this process works.
One of the nice things about working with master pages is that you can see the template in the IDE when you are creating the content pages. Because you can see the entire page while you are working on it, developing content pages that use a template is much easier. While you are working on the content page, all the templated items are shaded gray and are not editable. The only items you can alter are clearly shown in the template. These workable areas, called content areas, originally are defined in the master page itself. Within the master page, you specify the areas of the page that the content pages can use. You can have more than one content area in your master page if you want. Figure 16-3 shows the master page with a couple of content areas shown.
If you look at the screenshot from Figure 16-3, you can sort of see two defined areas on the page — these are content areas. Any content areas are represented in the Design view of the page by a light dotted box that represents the ContentPlaceHolder control. Also, if you hover your mouse over the content area, the name of the control appears above the control (although lightly). This hovering is also shown in action in Figure 16-3.
Many companies and organizations find using master pages ideal, because the technology closely models their typical business requirements. Many companies apply a common look and feel across their intranet. They can provide the divisions of their company with a .master file to use when creating a department’s section of the intranet. This process makes keeping a consistent look and feel across its entire intranet quite easy for the company.
Now it’s time to look at building the master page shown previously in Figure 16-3. You can create one in any text-based editor, but of course we recommend you use Visual Studio 2012 or Visual Studio Express 2012 for Web. This chapter demonstrates how to do it with Visual Studio 2012.
You add master pages to your projects the same way you add regular .aspx pages — choose the Master Page option when you add a new file to your application, as shown in Figure 16-4.
Because it’s quite similar to any other .aspx page, the Add New Item dialog box enables you to choose from a master page that uses the inline coding model or a master page that places its code in a separate file. Not placing your server code in a separate file means that you use the inline code model for the page you are creating. This option creates a single .master page. Choosing the option to place your code in a separate file means that you use the new code-behind model with the page you are creating. Selecting the “Place code in separate file” check box creates a single .master page, along with an associated .master.vb or .master.cs file. You also have the option of nesting your master page within another master page by selecting the Select master page option, but this is covered later in this chapter.
A sample master page that uses the inline-coding model is shown in Listing 16-1.
LISTING 16-1: A sample master page
<%@ Master Language="C#" %>
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>My Company Master Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<table cellpadding="3" border="1">
<tr style="background:silver">
<td colspan="2">
<h1>My Company Home Page</h1>
</td>
</tr>
<tr>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1"
runat="server">
</asp:ContentPlaceHolder>
</td>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2"
runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
<tr>
<td colspan="2">
Copyright 2012 - My Company
</td>
</tr>
</table>
</form>
</body>
</html>
This is a simple master page. The great thing about creating master pages in Visual Studio 2012 is that you can work with the master page in Code view, but you can also switch over to Design view to create your master pages just as you would any other ASP.NET page.
Start by reviewing the code for the master page. The first line is the directive:
<%@ Master Language="C#" %>
Instead of using the Page directive, as you would with a typical .aspx page, you use the Master directive for a master page. This master page uses only a single attribute, Language. The Language attribute’s value here is C#, but of course, you can also use VB if you are building a Visual Basic master page.
You code the rest of the master page just as you would any other .aspx page. You can use server controls, raw HTML and text, images, events, or anything else you normally would use for any .aspx page. This means that your master page can have a Page_Load event as well or any other event that you deem appropriate.
In the code shown in Listing 16-1, notice the use of the server control — the <asp:ContentPlaceHolder> control. This control defines the areas of the template where the content page can place its content:
<tr>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1"
runat="server">
</asp:ContentPlaceHolder>
</td>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2"
runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
In the case of this master page, two defined areas exist where the content page can place content. The master page contains a header and a footer area. It also defines two areas in the page where any inheriting content page can place its own content. You see how a content page uses this master page in the next section.
Now that you have a master page in place in your application, you can use this new template for any content pages in your application. Right-click the application in the Solution Explorer and choose Add New Item to create a new content page within your application.
To create a content page or a page that uses this master page as its template, you select a typical Web Form from the list of options in the Add New Item dialog box (see 16-5). Instead of creating a typical Web Form, however, you select the Select master page check box. This gives you the option of associating this Web Form later to some master page.
After you name your content page and click the Add button in the Add New Item dialog box, the Select a Master Page dialog box appears, as shown in Figure 16-6.
This dialog box enables you to choose the master page from which you want to build your content page. You choose from the available master pages that are contained within your application. For this example, select the new master page that you created in Listing 16-1 and click OK. This creates the content page. The created page is a simple .aspx page with only a couple of lines of code contained in the file, as shown in Listing 16-2.
LISTING 16-2: The created content page
C#
<%@ Page Language="C#" MasterPageFile="~/Wrox.master" Title="" %>
<script runat="server">
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1"
Runat="Server">
</asp:Content>
This content page is not much different from the typical .aspx page you have coded in the past. The big difference is the inclusion of the MasterPageFile attribute within the Page directive. The use of this attribute indicates that this particular .aspx page constructs its controls based on another page. The location of the master page within the application is specified as the value of the MasterPageFile attribute.
The other big difference is that it contains neither the <form id="form1" runat="server"> tag nor any opening or closing HTML tags that would normally be included in a typical .aspx page.
This content page may seem simple, but if you switch to the Design view within Visual Studio 2012, you see the power of using content pages. Figure 16-7 shows what you get with visual inheritance.
In this screenshot, you can see that just by using the MasterPageFile attribute in the Page directive, you are able to visually inherit everything that the Wrox.master file exposes. From the Design view within Visual Studio, you can also see what master page you are working with because the name of the referenced master page appears in the upper-right corner of the Design view page. If you try to click into the gray area that represents what is inherited from the master page, your cursor changes to show you are not allowed, as illustrated in Figure 16-8 (the cursor is on the word Page in the title).
All the common areas defined in the master page are shown in gray, whereas the content areas that you specified in the master page using the <asp:ContentPlaceHolder> server control are shown clearly and are available for additional content in the content page. You can add any content to these defined content areas as if you were working with a regular .aspx page. Listing 16-3 shows an example of using this .master page for a content page.
LISTING 16-3: The content page that uses Wrox.master
VB
<%@ Page Language="VB" MasterPageFile="~/Wrox.master" %>
<script runat="server">
Protected Sub Button1_Click(ByVal sender As Object,
ByVal e As System.EventArgs)
Label1.Text = "Hello " & HttpUtility.HtmlEncode(TextBox1.Text) & "!"
End Sub
</script>
<asp:Content ID="Content1" ContentPlaceHolderId="ContentPlaceHolder1"
runat="server">
<b>Enter your name:</b><br />
<asp:Textbox ID="TextBox1" runat="server" />
<br />
<br />
<asp:Button ID="Button1" runat="server" Text="Submit"
OnClick="Button1_Click" /><br />
<br />
<asp:Label ID="Label1" runat="server" Font-Bold="True" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="ContentPlaceHolder2"
runat="server">
<asp:Image ID="Image1" runat="server" ImageUrl="wrox.gif" />
</asp:Content>
C#
<%@ Page Language="C#" MasterPageFile="~/Wrox.master" %>
<script runat="server">
protected void Button1_Click(object sender, System.EventArgs e)
{
Label1.Text = "Hello " + HttpUtility.HtmlEncode(TextBox1.Text) + "!";
}
</script>
Right away you see some differences. As stated before, this page has no <form id="form1" runat="server"> tag nor any opening or closing <html> tags. These tags are not included because they are located in the master page. Also notice the server control — the <asp:Content> server control:
<asp:Content ID="Content1" ContentPlaceHolderId="ContentPlaceHolder1"
runat="server">
. . .
</asp:Content>
The <asp:Content> server control is a defined content area that maps to a specific <asp:ContentPlaceHolder> server control on the master page. In this example, you can see that the <asp:Content> server control maps itself to the <asp:ContentPlaceHolder> server control in the master page that has the ID of ContentPlaceHolder1. Within the content page, you don’t have to worry about specifying the location of the content because it is already defined within the master page. Therefore, your only concern is to place the appropriate content within the provided content sections, allowing the master page to do most of the work for you.
Just as when you work with any typical .aspx page, you can create any event handlers for your content page. In this case, you are using just a single event handler — the button click when the end user submits the form. The created .aspx page that includes the master page and content page material is shown in Figure 16-9.
One interesting point: When you use master pages, you are not tying yourself to a specific coding model (inline or code-behind), nor are you tying yourself to the use of a specific language. You can mix these elements within your application because they all work well.
You can use the master page created earlier, knowing that it was created using the inline-coding model, and then build your content pages using the code-behind model. Listing 16-4 shows a content page created using a Web Form that uses the code-behind option.
LISTING 16-4: A content page that uses the code-behind model
.ASPX (VB)
<%@ Page Language="VB" MasterPageFile="~/Wrox.master" AutoEventWireup="false"
CodeFile="MyContentPage.aspx.cs" Inherits="MyContentPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID=»Content2» ContentPlaceHolderId="ContentPlaceHolder1"
runat=»server»>
<b>Enter your name:</b><br />
<asp:Textbox ID="TextBox1" runat="server" />
<br />
<br />
<asp:Button ID="Button1" runat=»server" Text="Submit" /><br />
<br />
<asp:Label ID="Label1" runat="server" Font-Bold="True" />
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderId="ContentPlaceHolder2"
runat="server">
<asp:Image ID="Image1" runat="server" ImageUrl="wrox.gif" />
</asp:Content>
VB CODE-BEHIND
Partial Class MyContentPage
Inherits System.Web.UI.Page
Protected Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Label1.Text = "Hello " & HttpUtility.HtmlEncode(TextBox1.Text) & "!"
End Sub
End Class
C# CODE-BEHIND
public partial class MyContentPage : System.Web.UI.Page
{
protected void Button1_Click (object sender, System.EventArgs e)
{
Label1.Text = "Hello " + HttpUtility.HtmlEncode(TextBox1.Text) + "!";
}
}
Not only can you mix the coding models when using master pages, you can also mix the programming languages you use for the master or content pages. Just because you build a master page in C# doesn’t mean that you are required to use C# for all the content pages that use this master page. You can also build content pages in Visual Basic. For a good example, create a master page in C# that uses the Page_Load event handler and then create a content page in Visual Basic. When it is complete, run the page. It works perfectly well. This means that even though you might have a master page in one of the available .NET languages, the programming teams that build applications from the master page can use whatever .NET language they want. You have to love the openness that the .NET Framework offers!
You just observed that specifying at page level which master page to use is quite easy. In the Page directive of the content page, you simply use the MasterPageFile attribute:
<%@ Page Language="VB" MasterPageFile="~/Wrox.master" %>
Besides specifying the master page that you want to use at the page level, you have a second way to specify which master page you want to use in the web.config file of the application, as shown in Listing 16-5.
LISTING 16-5: Specifying the master page in the web.config file
<configuration>
<system.web>
<pages masterPageFile="~/Wrox.master" />
</system.web>
</configuration>
Specifying the master page in the web.config file causes every single content page you create in the application to inherit from the specified master page. If you declare your master page in the web.config file, you can create any number of content pages that use this master page. Once specified in this manner, you can then construct the content page’s Page directive like this:
<%@ Page Language="C#" %>
You can easily override the application-wide master page specification by simply declaring a different master page within your content page:
<%@ Page Language="VB" MasterPageFile="~/MyOtherCompany.master" %>
By specifying the master page in the web.config file, you are not really saying that you want all the .aspx pages to use this master page. If you create a normal Web Form and run it, ASP.NET will know that the page is not a content page and will run the page as a normal .aspx page.
If you want to apply the master page template to only a specific subset of pages (such as pages contained within a specific folder of your application), you can use the <location> element within the web.config file, as shown in Listing 16-6.
LISTING 16-6: Specifying the master page for a specific folder in the web.config file
<configuration>
<location path="AdministrationArea">
<system.web>
<pages masterPageFile="~/WroxAdmin.master" />
</system.web>
</location>
</configuration>
With the addition of this <location> section in the web.config file, you have now specified that a specific folder (AdministrationArea) will use a different master file template. You do so using the path attribute of the <location> element. The value of the path attribute can be a folder name as shown, or it can even be a specific page — such as AdminPage.aspx.
When you create content pages in your application, by default all the content pages automatically use the title that is declared in the master page. For example, you have primarily been using a master page with the title My Company Master Page. Every content page that is created using this particular master page also uses the same My Company Master Page title. You can avoid this by specifying the page’s title using the Title attribute in the @Page directive in the content page. You can also work with the page title programmatically in your content pages. To accomplish this, in the code of the content page, you use the Master object. The Master object conveniently has a property called Title. The value of this property is the page title that is used for the content page. You code it as shown in Listing 16-7.
LISTING 16-7: Coding a custom page title for the content page
VB
<%@ Page Language="VB" MasterPageFile="~/Wrox.master" %>
<script runat="server">
Protected Sub Page_LoadComplete(ByVal sender As Object, _
ByVal e As System.EventArgs)
Master.Page.Title = "This page was generated on: " & _
DateTime.Now.ToString()
End Sub
</script>
C#
<%@ Page Language="C#" MasterPageFile="~/Wrox.master" %>
<script runat="server">
protected void Page_LoadComplete(object sender, EventArgs e)
{
Master.Page.Title = "This page was generated on: " +
DateTime.Now.ToString();
}
</script>
When working with master pages from a content page, you actually have access to the controls and the properties that the master page exposes. The master page, when referenced by the content page, exposes a property called Master. You use this property to get at control values or custom properties that are contained in the master page itself.
To see an example of this control you have, create a GUID (unique identifier) in the master page that you can retrieve on the content page that is using the master. For this example, use the master page that was created in Listing 16-1, but add a Label server control and the Page_Load event (see Listing 16-8).
LISTING 16-8: A master page that creates a GUID on the first request
VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
If Not Page.IsPostBack Then
Label1.Text = System.Guid.NewGuid().ToString()
End If
End Sub
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>My Company Master Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<table cellpadding="3" border="1">
<tr bgcolor="silver">
<td colspan="2">
<h1>My Company Home Page</h1>
<b>User's GUID:
<asp:Label ID="Label1" runat="server" /></b>
</td>
</tr>
<tr>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1"
runat="server">
</asp:ContentPlaceHolder>
</td>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2"
runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
<tr>
<td colspan="2">
Copyright 2012 - My Company
</td>
</tr>
</table>
</form>
</body>
</html>
C#
<%@ Master Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Label1.Text = System.Guid.NewGuid().ToString();
}
}
</script>
Now you have a Label control on the master page that you can access from the content page:
You have a couple of ways to accomplish this task. The first way is to use the FindControl() method that the master page exposes. Listing 16-9 shows this approach.
LISTING 16-9: Getting at the Label’s Text value in the content page
VB
<%@ Page Language="VB" MasterPageFile="~/Wrox.master" %>
<script runat="server" language="vb">
Protected Sub Page_LoadComplete(ByVal sender As Object, _
ByVal e As System.EventArgs)
Label1.Text = CType(Master.FindControl("Label1"), Label).Text
End Sub
Protected Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Label2.Text = "Hello " & TextBox1.Text & "!"
End Sub
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="ContentPlaceHolder1"
runat="server">
<b>Your GUID number from the master page is:<br />
<asp:Label ID="Label1" runat="server" /></b><p>
<b>Enter your name:</b><br />
<asp:Textbox ID="TextBox1" runat="server" />
<br />
<br />
<asp:Button ID="Button1" runat="server" Text="Submit"
OnClick="Button1_Click" /><br />
<br />
<asp:Label ID="Label2" runat="server" Font-Bold="True" /></p>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderId="ContentPlaceHolder2"
runat="server">
<asp:Image ID="Image1" runat="server" ImageUrl="Wrox.gif" />
</asp:Content>
C#
<%@ Page Language="C#" MasterPageFile="~/Wrox.master" %>
<script runat="server">
protected void Page_LoadComplete(object sender, EventArgs e)
{
Label1.Text = (Master.FindControl("Label1") as Label).Text;
}
protected void Button1_Click(object sender, EventArgs e)
{
Label2.Text = "<b>Hello " + TextBox1.Text + "!</b>";
}
</script>
In this example, the master page in Listing 16-8 creates a GUID that it stores as a text value in a Label server control on the master page itself. The ID of this Label control is Label1. The master page generates this GUID only on the first request for this particular content page. From here, you then populate one of the content page’s controls with this value.
The interesting thing about the content page is that you put code in the Page_LoadComplete event handler so that you can access the GUID value that is on the master page. This event in ASP.NET fires immediately after the Page_Load event fires. Event ordering is covered later in this chapter, but the Page_Load event in the content page always fires before the Page_Load event in the master page. To access the newly created GUID (if it is created in the master page’s Page_Load event), you have to get the GUID in an event that comes after the Page_Load event — and that is where Page_LoadComplete comes into play. Therefore, within the content page’s Page_LoadComplete event, you populate a Label server control within the content page itself. Note that the Label control in the content page has the same ID as the Label control in the master page, but this doesn’t make a difference. You can differentiate between them with the use of the Master property.
As previously mentioned there is also a second option: Not only can you access the server controls in the master page in this way, you can access any custom properties the master page might expose as well. Look at the master page shown in Listing 16-10; it uses a custom property for the <h1> section of the page.
LISTING 16-10: A master page that exposes a custom property
VB
<%@ Master Language="VB" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
If Not Page.IsPostBack Then
Label1.Text = Guid.NewGuid().ToString()
End If
End Sub
Dim m_PageHeadingTitle As String = "My Company"
Public Property PageHeadingTitle() As String
Get
Return m_PageHeadingTitle
End Get
Set(ByVal Value As String)
m_PageHeadingTitle = Value
End Set
End Property
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>My Company Master Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="Form1" runat="server">
<table cellpadding="3" border="1">
<tr bgcolor="silver">
<td colspan="2">
<h1><%= PageHeadingTitle %></h1>
<b>User's GUID:
<asp:Label ID="Label1" runat="server" /></b>
</td>
</tr>
<tr>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1"
runat="server">
</asp:ContentPlaceHolder>
</td>
<td>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2"
runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
<tr>
<td colspan="2">
Copyright 2010 - My Company
</td>
</tr>
</table>
</form>
</body>
</html>
C#
<%@ Master Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Label1.Text = System.Guid.NewGuid().ToString();
}
}
string m_PageHeadingTitle = "My Company";
public string PageHeadingTitle
{
get
{
return m_PageHeadingTitle;
}
set
{
m_PageHeadingTitle = value;
}
}
</script>
In this master page example, the master page is exposing the property you created called PageHeadingTitle. A default value of "My Company" is assigned to this property. You then place it within the HTML of the master page file between some <h1> elements, which makes the default value become the heading used on the page within the master page template. Although the master page already has a value it uses for the heading, any content page that is using this master page can override the <h1> title heading. Listing 16-11 shows the process.
LISTING 16-11: A content page that overrides the property from the master page
VB
<%@ Page Language="VB" MasterPageFile="~/Wrox.master" %>
<%@ MasterType VirtualPath="~/Wrox.master" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
Master.PageHeadingTitle = "My Company-Division X"
End Sub
</script>
C#
<%@ Page Language="C#" MasterPageFile="~/Wrox.master" %>
<%@ MasterType VirtualPath="~/Wrox.master" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Master.PageHeadingTitle = "My Company-Division X";
}
</script>
From the content page, you can assign a value to the property that is exposed from the master page by the use of the Master property. This is quite simple to do. Remember that not only can you access any public properties that the master page might expose, but you can also retrieve any methods that the master page contains as well.
The item that makes this all possible is the MasterType page directive. Using the MasterType directive enables you to make a strongly typed reference to the master page and access the master page’s properties via the Master object.
Earlier, you saw how to access the server controls that are on the master page by using the FindControl() method. The FindControl() method works fine, but it is a late-bound approach, and as such, the method call may fail if the control is removed from markup. Use defensive coding practices and always check for null when returning objects from FindControl(). Using the mechanics just illustrated (with the use of public properties shown in Listing 16-10), you can use another approach to expose any server controls on the master page. You may find this approach to be more effective.
To take this different approach, you simply expose the server control as a public property, as shown in Listing 16-12.
LISTING 16-12: Exposing a server control from a master page as a public property
VB
<%@ Master Language="VB" %>
<script runat="server">
Public Property MasterPageLabel1() As Label
Get
Return Label1
End Get
Set(ByVal Value As Label)
Label1 = Value
End Set
End Property
</script>
C#
<%@ Master Language="C#" %>
<script runat="server">
public Label MasterPageLabel1
{
get
{
return Label1;
}
set
{
Label1 = value;
}
}
</script>
In this case, a public property called MasterPageLabel1 provides access to the Label control that uses the ID of Label1. You can now create an instance of the MasterPageLabel1 property on the content page and override any of the attributes of the Label server control. So if you want to increase the size of the GUID that the master page creates and displays in the Label1 server control, you can simply override the Font.Size attribute of the Label control, as shown in Listing 16-13.
LISTING 16-13: Overriding an attribute from the Label control that is on the master page
VB
<%@ Page Language"VB" MasterPageFile"~/Wrox.master" %>
<%@ MasterType VirtualPath"~/Wrox.master" %>
<script runat"server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
Master.MasterPageLabel1.Font.Size = 25
End Sub
</script>
C#
<%@ Page Language"C#" MasterPageFile"~/Wrox.master" %>
<%@ MasterType VirtualPath"~/Wrox.master" %>
<script runat"server">
protected void Page_Load(object sender, EventArgs e)
{
Master.MasterPageLabel1.Font.Size = 25;
}
</script>
This approach may be the most effective way to access any server controls that the master page exposes to the content pages.
As you have seen, the master page enables you to specify content areas the content page can use. Master pages can consist of just one content area, or they can have multiple content areas. The nice thing about content areas is that when you create a master page, you can specify default content for the content area. You can leave this default content in place to be utilized by the content page if you choose not to override it. Listing 16-14 shows a master page that specifies some default content within a content area.
LISTING 16-14: Specifying default content in the master page
<%@ Master Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>My Company</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
Here is some default content.
</asp:ContentPlaceHolder><p>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2" runat="server">
Here is some more default content.
</asp:ContentPlaceHolder></p>
</form>
</body>
</html>
To place default content within one of the content areas of the master page, you simply put it in the ContentPlaceHolder server control on the master page itself. Any content page that inherits this master page also inherits the default content. Listing 16-15 shows a content page that overrides just one of the content areas from this master page.
LISTING 16-15: Overriding some default content in the content page
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" %>
<asp:Content ID="Content3" ContentPlaceHolderId="ContentPlaceHolder2"
runat="server">
Here is some new content.
</asp:Content>
This code creates a page with one content area that shows content coming from the master page itself in addition to other content that comes from the content page (see Figure 16-10).
The other interesting point when you work with content areas in the Design view of Visual Studio 2012 is that the smart tag enables you to work easily with the default content.
When you first start working with the content page, you will notice that all the default content is at first populated in all the Content server controls. You can change the content by clicking the control’s smart tag and selecting the Create Custom Content option from the provided menu. This option enables you to override the master page content and insert your own defined content. After you have placed some custom content inside the content area, the smart tag shows a different option — Default to Master’s Content. This option enables you to return the default content that the master page exposes to the content area and to erase whatever content you have already placed in the content area — thereby simply returning to the default content. If you choose this option, you will be warned that you are about to delete any custom content you placed within the Content server control (see Figure 16-11).
After changing the default content of one of the Content controls, the WYSIWYG view changes, too, as you can see in Figure 16-12.
From any content page, you can easily assign a master page programmatically. You assign the master page to the content page using the Page.MasterPageFile property. You can use this property regardless of whether another master page is already assigned in the @Page directive.
To assign a master page with the Page.MasterPageFile property you use the PreInit event. The PreInit event is the earliest point in which you can access the page life cycle. For this reason, this event is where you need to assign any master page that is used by any content pages. PreInit is an important event to make note of when you are working with master pages, because it is the only point where you can affect both the master and content page before they are combined into a single instance. Listing 16-16 shows how to assign the master page programmatically from the content page.
LISTING 16-16: Using Page_PreInit to assign the master page programmatically
VB
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs)
Page.MasterPageFile = "~/MyMasterPage.master"
End Sub
</script>
C#
<%@ Page Language="C#" %>
<script runat="server">
protected void Page_PreInit(object sender, EventArgs e)
{
Page.MasterPageFile = "~/MyMasterPage.master";
}
</script>
In this case, when the page is being generated dynamically, the master page is assigned to the content page in the beginning of the page construction process. Note that the content page must have the Content controls defined in the master page; otherwise, an error is thrown.
So far, you have been creating a single master page that the content page can use. Most companies and organizations, however, are not just two layers. Many divisions and groups exist within the organization that might want to use variations of the master by, in effect, having a master page within a master page. With ASP.NET, this type of page is quite possible.
For example, imagine that Reuters is creating a master page to be used throughout the entire company intranet. Not only does the Reuters enterprise want to implement this master page companywide, but various divisions within Reuters also want to provide templates for the subsections of the intranet directly under their control. For example, Reuters Europe and Reuters America each want their own unique master page, as illustrated in Figure 16-13.
To get these unique pages, the creators of the Reuters Europe and Reuters America master pages simply create a master page that inherits from the global master page, as shown in Listing 16-17. You can find this file, named ReutersMain.master, as part of the Chapter 16 code download for this book.
LISTING 16-17: The main master page
<%@ Master Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Reuters</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<p><asp:Label ID="Label1" runat="server" BackColor="LightGray"
BorderColor="Black" BorderWidth="1px" BorderStyle="Solid"
Font-Size="XX-Large"> Reuters Main Master Page </asp:Label></p>
<asp:ContentPlaceHolder ID=»ContentPlaceHolder1» runat=»server»>
</asp:ContentPlaceHolder>
</form>
</body>
</html>
This master page is simple, but excellent for showing you how this nesting capability works. The main master page is the master page used globally in the company. It has the ContentPlaceHolder server control with the ID of ContentPlaceHolder1.
You create a submaster or nested master page in the same manner as you would when building any other master page. From the Add New Item dialog box, select the Master Page option and make sure you have the Select master page option selected, as illustrated in Figure 16-14. Again, the Select a Master Page dialog box appears, in which you make a master page selection.
Listing 16-18 shows how you can work with this main master from a submaster file. You can find this file, named ReutersEurope.master, as part of the Chapter 16 code download for this book.
LISTING 16-18: The submaster page
<%@ Master Language="C#" MasterPageFile="~/ReutersMain.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="ContentPlaceHolder1"
runat="server">
<asp:Label ID="Label1" runat="server" BackColor="#E0E0E0" BorderColor="Black"
BorderStyle="Dotted" BorderWidth="2px" Font-Size="Large">
Reuters Europe </asp:Label><br /><hr />
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</asp:Content>
Looking this page over, you can see that it isn’t much different than a typical .aspx page that makes use of a master page. The MasterPageFile attribute is used just the same, but instead of using the @Page directive, it uses the @Master page directive. Then the Content2 control also uses the ContentPlaceHolderId attribute of the Content control. This attribute ties this content area to the content area ContentPlaceHolder1, which is defined in the main master page.
One feature of ASP.NET is the ability to view nested master pages directly in the Design view of Visual Studio 2012. Figure 16-15 shows a nested master page in the Design view of Visual Studio 2012.
Within the submaster page presented in Listing 16-18, you can also now use as many ContentPlaceHolder server controls as you want. Any content page that uses this master can use these controls. Listing 16-19 shows a content page that uses the submaster page ReutersEurope.master.
LISTING 16-19: The content page
DEFAULT.ASPX
<%@ Page Language="C#" MasterPageFile="~/ReutersEurope.master" %>
<asp:Content ID="Content1" ContentPlaceHolderId="ContentPlaceHolder1"
runat="server">
Hello World
</asp:Content>
As you can see, in this content page the value of the MasterPageFile attribute in the @Page directive is the submaster page that you created. Inheriting the ReutersEurope master page actually combines both master pages (ReutersMain.master and ReutersEurope.master) into a single master page. The Content control in this content page points to the content area defined in the submaster page as well. You can see this in the code with the use of the ContentPlaceHolderId attribute. In the end, you get a very non-artistic page, as shown in Figure 16-16.
In many cases, developers are building applications that will be viewed in a multitude of different containers. Some viewers may view the application in Microsoft Internet Explorer and some might view it using Firefox or Google Chrome. Still other viewers may call up the application on a tablet or cell phone.
For this reason, ASP.NET enables you to use multiple master pages within your content page. Depending on the viewing container used by the end user, the ASP.NET engine pulls the appropriate master file. Therefore, you want to build container-specific master pages to provide your end users with the best possible viewing experience by taking advantage of the features that a specific container provides. Listing 16-20 demonstrates the capability to use multiple master pages.
LISTING 16-20: A content page that can work with more than one master page
<%@ Page Language="C#" MasterPageFile="~/Wrox.master"
Firefox:MasterPageFile="~/WroxFirefox.master"
Safari:MasterPageFile="~/WroxSafari.master"
Opera:MasterPageFile="~/WroxOpera.master"
Chrome:MasterPageFile="~/WroxChrome.master" %></asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="ContentPlaceHolder1"
runat="server">
Hello World
</asp:Content>
As you can see from this example content page, it can work with three different master page files. The first one uses the attribute MasterPageFile. It is the default setting used for any page that doesn’t fit the criteria for any of the other options. This means that if the requestor is not a Firefox, Chrome, Safari, or Opera browser, the default master page, Wrox.master, is used. However, if the requestor is a Chrome browser, WroxChrome.master is used instead, as illustrated in Figure 16-17.
You can find a list of available browsers on the production server where the application will be hosted at C:\Windows\Microsoft.NET\Framework\v4.0.xxxxx\CONFIG\Browsers. Some of the available options include the following:
Of course, you can also add any additional .browser files that you deem necessary.
When you work with master pages and content pages, both can use the same events (such as the Load event). Be sure you know which events come before others. This is one of the most important aspects of master pages, since there are differences compared to the event order of regular pages.
You are bringing two classes together to create a single page class, and a specific order is required. When an end user requests a content page in the browser, the event ordering is as follows:
Pay attention to this event ordering when building your applications. If you want to use server control values that are contained on the master page within a specific content page, for example, you can’t retrieve the values of these server controls from within the content page’s Page_Load event. This is because this event is triggered before the master page’s Page_Load event. This problem prompted the creation of the Page_LoadComplete event in the .NET Framework. The content page’s Page_LoadComplete event follows the master page’s Page_Load event. You can, therefore, use this ordering to access values from the master page even though it isn’t populated when the content page’s Page_Load event is triggered.
When working with typical .aspx pages, you can apply output caching to the page by using the following construct (or variation thereof):
<%@ OutputCache Duration="10" Varybyparam="None" %>
This line caches the page in the server’s memory for 10 seconds. Many developers use output caching to increase the performance of their ASP.NET pages. Using it on pages with data that doesn’t become stale too quickly also makes a lot of sense.
How do you go about applying output caching to ASP.NET pages when working with master pages? You cannot apply caching to just the master page. You cannot put the OutputCache directive on the master page itself. If you do so, on the page’s second retrieval, you get an error because the application cannot find the cached page.
To work with output caching when using a master page, place the OutputCache directive in the content page. Doing so caches both the contents of the content page and the contents of the master page (remember, it is just a single page at this point). Placing the OutputCache directive in the master page does not cause the master page to produce an error, but it will not be cached. This directive works in the content page only.
Another new and interesting feature of ASP.NET 4+ in regards to working with any caching capabilities is that ASP.NET now enables you to control view state at the control level. Although you might immediately think of being able to control view state in this manner with controls such as the GridView or something that generally has a lot of view state, you can also use this new capability with the ContentPlaceHolder control.
For example, you can construct something such as the following:
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server"
ViewStateMode="Disabled">
</asp:ContentPlaceHolder>
In this case, ContentPlaceHolder1 will not use view state even if the rest of the page is using it. The available options for the ViewStateMode property include Disabled, Enabled, and Inherit. Disabled turns off view state for the control, Enabled turns it on, and Inherit takes the value that is assigned in the @Page directive. Removing view state improves the performance of your pages.
Many of the larger ASP.NET applications today make use of master pages and the power this technology provides in building templated websites. ASP.NET includes ASP.NET AJAX as part of the default install, and you will find that master pages and Ajax go together quite well.
Every page that is going to make use of Ajax capabilities must have the ScriptManager control on the page. If the page you want to use Ajax with is a content page making use of a master page, you must place the ScriptManager control on the master page itself.
It isn’t too difficult to set up your master page so that it is Ajax-enabled. To do this, simply add a ScriptManager server control to the master page itself. Listing 16-21 shows an example of this in action.
LISTING 16-21: The Ajax master page
<%@ Master Language="C#" %>
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
As you can see from Listing 16-21, the only real difference between this Ajax master page and the standard master page is the inclusion of the ScriptManager server control. You want to use this technique if your master page includes any Ajax capabilities whatsoever, even if the content page makes no use of Ajax at all.
The ScriptManager control on the master page also is beneficial if you have common JavaScript items to place on all the pages of your web application. For example, Listing 16-22 shows how you could easily include JavaScript on each page through the master page.
LISTING 16-22: Including scripts through your master page
<%@ Master Language="C#" %>
<html xmlns"http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Path="myScript.js" />
</Scripts>
</asp:ScriptManager>
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
In this example, the myScript.js file is included on every content page that makes use of this Ajax master page. If your content page also needs to make use of Ajax capabilities, you cannot simply add another ScriptManager control to the page. Instead, the content page needs to make use of the ScriptManager control that is already present on the master page.
That said, if your content page needs to add additional items to the ScriptManager control, it is able to access this control on the master page using the ScriptManagerProxy server control. Using the ScriptManagerProxy control enables you to add any items to the ScriptManager that are completely specific to the instance of the content page that makes the inclusions.
For example, Listing 16-23 shows how a content page adds additional scripts to the page through the ScriptManagerProxy control.
LISTING 16-23: Adding additional items using the ScriptManagerProxy control
<%@ Page Language="C#" MasterPageFile="~/AjaxMaster.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1"
runat="Server">
<asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
<Scripts>
<asp:ScriptReference Path="myOtherScript.js" />
</Scripts>
</asp:ScriptManagerProxy>
</asp:Content>
In this case, this content page uses the ScriptManagerProxy control to add an additional script to the page. This ScriptManagerProxy control works exactly the same as the main ScriptManager control, except that it is meant for content pages making use of a master page. The ScriptManagerProxy control then interacts with the page’s ScriptManager control to perform the necessary actions.
When you create applications that use a common header, footer, or navigation section on nearly every page of the application, master pages are a great solution. Master pages are easy to implement and enable you to make changes to each and every page of your application by changing a single file. Imagine how much easier this method makes managing large applications that contain thousands of pages.
This chapter described master pages in ASP.NET and explained how you build and use master pages within your web applications. In addition to the basics, the chapter covered master page event ordering, caching, and specific master pages for specific containers. In the end, when you are working with templated applications, master pages should definitely be considered.