Chapter 3: Pages and Design (Part 1 of 2)

This article is an excerpt from Inside Microsoft Windows SharePoint Services 3.0 by Ted Pattison and Daniel Larson, from Microsoft Press (ISBN 9780735623200, copyright 2007 by Microsoft Press, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Download the sample code that accompanies the book

  • Understand the benefits and restrictions of safe mode processing.

  • Learn how to design site pages with controls and Web Part zones.

  • Learn how to customize the master page used by site pages.

  • Learn how to customize the Windows SharePoint Services navigation infrastructure.

  • Learn best practices for branding Windows SharePoint Services sites.

Contents

  • Site Page Fundamentals

  • Designing Site Pages by Using Controls

Site Page Fundamentals

Chapter 2, "SharePoint Architecture," introduced you to the key differences between application pages and site pages. You learned that application pages have an advantage over site pages in that they perform better and provide a developer with the ability to add in-line code. You also learned that site pages have some key advantages over application pages because they can be created dynamically and can also be customized by users on a site-by-site basis.

Chapter 2 discussed the role of the SPVirtualPathProvider component and introduced the principles of page ghosting and unghosting. As you remember, page ghosting is an optimization used with site pages in which a single page template can be used to process multiple page instances across many different sites. For example, the home page for every team site in a Windows SharePoint Services farm is based on an underlying page template named default.aspx that resides on the file system of the front-end Web server. A page template, such as default.aspx, is compiled into an assembly dynamic-link library (DLL) and loaded into memory just once per Web application. However, this page template and its efficient usage of memory can still be used to serve up pages for thousands of sites. This is an obvious advantage toward scalability.

When a user customizes a site page by using Microsoft Office SharePoint Designer and then saves those changes, a customized version of the page definition is stored in the content database. While this provides flexibility from a customization standpoint, it also can have a negative impact on performance and scalability. When the customized page is requested, its page definition must be retrieved from the backend database server by the SPVirtualPathProvider component and then fed to the Microsoft ASP.NET compiler, where it is parsed and loaded into memory. You can imagine that a Web application with thousands of customized pages requires more memory because each customized page definition must be separately parsed and loaded into memory within the application pool that is hosting the current Web application.

You should note that customized pages are not processed by using the standard ASP.NET model in which a page is compiled into an assembly DLL. Instead, customized pages are parsed by the ASP.NET page parser and then processed using the no-compile mode feature that was introduced with ASP.NET 2.0.

As a developer, your initial reaction to this might be to question why customized pages are processed in no-compile mode. Your instincts likely tell you that compiled pages run faster than no-compile pages. However, no-compile pages can be more efficient and more scalable in certain scenarios. This is especially true in a large Windows SharePoint Services environment where the number of customized pages can reach into the thousands or tens of thousands.

No-compile pages can be loaded into memory and then unloaded in a manner that is not possible for compiled pages because the Microsoft .NET Framework doesn't really support the concept of unloading an assembly DLL from memory. The closest equivalent would be to recycle the current Windows process or the current .NET AppDomain class. However, this type of recycling involves unloading all assembly DLLs from memory, not just those assembly DLLs that haven't been used recently. Furthermore, the .NET Framework places an upper limit on the number of assembly DLLs that can be loaded into a .NET AppDomain.

No-compile pages provide higher levels of scalability because they do not require loading new assembly DLLs or managed classes into memory. Instead, the processing of no-compile pages involves loading control trees into memory. Windows SharePoint Services can manage the memory usage for the control trees associated with customized pages more efficiently because they are not compiled into assembly DLLs. For example, once Windows SharePoint Services has finished processing a customized page, it can unload the page's control tree to free up memory for other purposes. Furthermore, no-compile pages eliminate the need to go through the compilation process, which actually provides faster response times for pages upon first access.

Programming with SPFile Objects

Windows SharePoint Services tracks each site page as a file within the content database. You can access a site page through the Windows SharePoint Services object model by using the SPFile object. For example, assume that you want to program against the home page for a site. You can obtain a reference to the required SPFile object by using the GetFile method of a SPWeb object.

SPWeb site = SPContext.Current.Web; 
SPFile homePage = site.GetFile("default.aspx");

The SPFile class makes it possible to read and write to the contents of a site page. For example, the OpenBinary method of an SPFile object returns a binary array containing the page contents. The OpenBinaryStream method returns a System.IO.Stream object. Each of these methods provides an approach for reading the contents of a site page. An SPFile object also provides a SaveBinary method that allows you to update the contents of a site page as well. Note that updating the contents of a site page by using this method customizes the page and moves it into a customized or unghosted state.

The SPFile class provides several other methods for managing site pages within a site such as Delete, MoveTo, and CopyTo. The Delete method, as its name implies, removes the target file from the site. MoveTo makes it possible to move a file, such as a site page, to another location so that it's accessible through a different URL. CopyTo allows you to clone a site page with a copy. Note that if you call CopyTo on an uncustomized page, it creates another uncustomized page. Likewise, if you call CopyTo on a customized page, it results in the creation of a customized page in an unghosted state.

Tip

Ghosted and uncustomized are terms used to describe site pages served up using file system templates. Unghosted and customized both refer to pages that exist entirely in the database, which no longer depend on a file system template.

The SPWeb object for a site also exposes a Files property with a public Add method that allows you to add new site pages. There is an overloaded version of the Add method that allows you to pass a stream object with the contents of the new page. The following example demonstrates writing the contents of a new page to a MemoryStream object and then using it to create a new site page named Hello.htm.

// write out new page in memory stream 
MemoryStream stream = new MemoryStream(); 
StreamWriter writer = new StreamWriter(stream); 
writer.WriteLine("<html><body>"); 
writer.WriteLine("Hello, World"); 
writer.WriteLine("</body></html>"); 
writer.Flush(); 

// add new page to site 
SPWeb site = SPContext.Current.Web; 
site.Files.Add("hello.htm", stream);

Note that the Add method doesn't support adding a new site page that is associated with an underlying page template. Therefore, site pages created by using the Add method are always created as customized pages in an unghosted state.

The SPFile class provides a CustomizedPageStatus property that makes it possible to determine whether a site page has been customized and placed in an unghosted state. The CustomizedPageStatus property is based on an enumeration type named SPCustomizedPageStatus. If a SPFile object for a site page has a CustomizedPageStatus property value of Uncustomized, it means that the page is still in a ghosted state. A site page with a CustomizedPageStatus property value of Customized has been customized and is in an unghosted state. The SPFile object also provides a method named RevertContentStream that removes any customizations and returns an unghosted page to its initial ghosted state.

SPFolder Objects

The files within a SharePoint site are structured within a hierarchy of folders. Each folder is represented in the Windows SharePoint Services object model with an SPFolder object. Each SPFolder object contains a Files property that allows you to enumerate through its files. If you want to enumerate through all of the files at the root folder of a site, you can access the RootFolder property of a SPWeb object and then use a foreach loop to enumerate through all of its files.

SPWeb site = SPContext.Current.Web; 
SPFolder rootFolder = site.RootFolder; 

foreach (SPFile file in rootFolder.Files){ 
  // process each file 
}

The Windows SharePoint Services object model also makes it possible to enumerate through the folders within a folder. This, in turn, makes it possible to write code that enumerates through all of the folders within a site to discover all existing files. The following code displays an example of custom code that starts at the root folder of a site and uses recursion to populate an ASP.NET TreeView control.

const string SITE_IMG = @"\_layouts\images\FPWEB16.GIF"; 
const string FOLDER_IMG = @"\_layouts\images\FOLDER16.GIF"; 
const string GHOSTED_FILE_IMG = @"\_layouts\images\NEWDOC.GIF"; 
const string UNGHOSTED_FILE_IMG = @"\_layouts\images\RAT16.GIF"; 

protected override void OnLoad(EventArgs e) { 
  SPWeb site = SPContext.Current.Web; 
  SPFolder rootFolder = site.RootFolder; 
  TreeNode rootNode = new TreeNode(site.Url, site.Url, SITE_IMG); 
  LoadFolderNodes(rootFolder, rootNode); 
  treeSiteFiles.Nodes.Add(rootNode); 
  treeSiteFiles.ExpandDepth = 1; 
} 

protected void LoadFolderNodes(SPFolder folder, TreeNode folderNode) { 
  foreach (SPFolder childFolder in folder.SubFolders) { 
    TreeNode childFolderNode = new TreeNode(childFolder.Name,  
                                            childFolder.Name,  
                                            FOLDER_IMG); 
    LoadFolderNodes(childFolder, childFolderNode); 
    folderNode.ChildNodes.Add(childFolderNode); 
  } 

  foreach (SPFile file in folder.Files) { 
    TreeNode fileNode; 
    if (file.CustomizedPageStatus == SPCustomizedPageStatus.Uncustomized) { 
      fileNode = new TreeNode(file.Name, file.Name, GHOSTED_FILE_IMG); 
    } 
    else { 
      fileNode = new TreeNode(file.Name, file.Name, 
        UNGHOSTED_FILE_IMG); 
    } 
    folderNode.ChildNodes.Add(fileNode); 
  } 
}

Note that this code is also written to provide different images that allow the user to distinguish between pages that are customized and those that are uncustomized. A graphic of the resulting TreeView control is shown in Figure 3-1.

Figure 3-1. A SharePoint site contains a hierarchy of folders and files; files such as .aspx and .htm pages can either be in an uncustomized or customized state

Working with Page Templates

Up to this point, the discussion of page templates has revolved around using the standard page templates that are built into Windows SharePoint Services. It is now time to explore how to create your own page templates and integrate them into a custom business solution. You can create and integrate custom page templates by using either a feature or a site definition. Because we have not yet discussed site definitions, this chapter focuses on the use of custom page templates within the context of a feature.

Examples of using page templates in this chapter are based on a Microsoft Visual Studio project named CustomSitePages that contains a feature of the same name. (The project is included on the companion Web site for this book.) Figure 3-2 displays the Solution Explorer window for this project. As you can see, the project contains a feature.xml and an elements.xml file like the other features that we built in earlier chapters. However, this feature also contains several .aspx files that are used to define site page templates, such as Page01.aspx and Page02.aspx. The CustomSitePages project also contains several ASP.NET user controls as well as the code for an ASP.NET custom control that will be discussed later in this chapter.

If you open and build the CustomSitePages project, you find a post-build event that runs a batch file named Install.bat. This batch file copies the feature files along with the page templates into the proper location within the TEMPLATE directory and then installs the CustomSitePages feature by using the stsadm.exe command-line utility. Note that the CustomSitePages feature is designed to activate within the context of a site. After the feature is installed, you can activate it within any site in the current farm and follow along with these examples.

Figure 3-2. The CustomSitePages project demonstrates how to build a feature with custom site page templates

When the CustomSitePages feature is activated, it contains declarative logic in elements.xml to provision site page instances from its page templates. The code in the FeatureActivated event extends the navigation components of a SharePoint site by adding two new drop-down menus to the top link bar with menu items to navigate to the newly provisioned site page instance. The technique for adding these drop-down menus to the top link bar is explained later in this chapter.

Let's start with a simple definition for a page template. Examine the following definition for the page template named Page01.aspx.

<%@ Page MasterPageFile="~masterurl/default.master" 
    meta:progid="SharePoint.WebPartPage.Document"  %> 

<asp:Content  ContentPlaceHolderID="PlaceHolderMain"> 
  <h3>Hello World</h3> 
  A simple page template used to create site pages 
</asp:Content>

The @ Page directive at the top of this page template assigns a value of ~masterurl/default.master to the MasterPageFile attribute to link to the standard master page used by site pages within SharePoint sites. We will defer a more detailed discussion of master pages and the MasterPageFile attribute until later in this chapter. For now, simply assume that this site page template is designed to link to the standard master page.

You should also notice that the @ Page directive in the previous example contains a meta:progid attribute with a value of SharePoint.WebPartPage.Document. This attribute is included to make that page compatible with Office SharePoint Designer, and is also available in the SPFile object's ProgID property. Once site page instances have been provisioned by using this page template, users can open these pages with SharePoint Designer and customize their content.

This simple example demonstrates the power and elegance of master pages in development for Windows SharePoint Services. You should be impressed at how little text is needed to define a simple page template. All that's really required for a simple page template is to link to a master page and supply some unique content for the placeholder named PlaceHolderMain. As you learn more about how Windows SharePoint Services uses master pages, you will discover that there are many more named placeholders that you can optionally override within your page templates to enrich them with all types of content such as controls, scripts, and styles.

Now that you've seen how to create a simple page template, it's time to put it to use. Keep in mind that a page template, such as Page01.aspx, serves no purpose until you begin using it to provision site page instances. This can be done by creating a feature that contains a special type of element known as a Module.

A Module element can be thought of as a file set. When you create a Module, you add one or more inner File elements. The key point is that each File element is used to provision an instance of a file from a file template. Remember that the file template exists on the file system of the front-end Web server, whereas the file instance being provisioned is being created inside the context of a particular site.

In this particular case, we want to provision an instance of a site page from the page template named Page01.aspx. Note that the top-level directory of the CustomSitePages feature contains a nested directory named PageTemplates that contains all of the page templates. When you define a Module element, you can specify a Path attribute that points to a source directory, such as PageTemplates. You can also specify a Url element if you would like to instantiate the resulting site page instance within an inner folder instead of at the root folder of the target site.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 
  <Module Path="PageTemplates" Url="SitePages" > 
    <File Url="Page01.aspx" Type="Ghostable" />  
  </Module> 
</Elements>

Note that the File element within this example is created with a Url attribute that points to the source file for the page template. When you activate a feature that contains this Module element, Windows SharePoint Services provisions a site page instance within the target site at the following relative path.

SitePages/Page01.aspx

The user can navigate to this page by using the Site Pages drop-down menu and clicking on the menu item with a caption of Site Page 1. Figure 3-3 depicts the resulting site page instance.

Figure 3-3. A Module element allows you to provision a site page instance from page templates

Note that the File element in the previous example contains a Type attribute with a value of Ghostable. When a site page instance, such as Page01.aspx, is provisioned, it initially exists in an uncustomized state and benefits from the principles of page ghosting. This means that you can activate this feature in a thousand different sites within a Web application and that all sites use a single compiled version of the page. Page ghosting also makes it possible to make changes to the page template on the file system of the front-end Web server and have those changes affect all of the sites that have pages provisioned from this page template.

Only two possible settings exist for the Type attribute: Ghostable and GhostableInLibrary. These two settings are used to differentiate between files that are provisioned inside a document library and those that are not. In this case, the site page instance has a Type of Ghostable because it is not being provisioned inside a document library. Later in the chapter, you will encounter an example of a File element whose Type attribute value will be defined as GhostableInLibrary.

You should also note that when defining a File element, you can optionally include the Name element. This makes it possible to provision a site page instance with a name that differs from the name of the underlying page template. This technique wasn't used in the previous example, so the resulting site page instance was provisioned with the same name as the page template. However, you can extend the Module element shown earlier to provision several different site page instances from a single page template and give them all different names.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 
  <Module Path="PageTemplates" Url="SitePages" > 
    <File Url="Page01.aspx" Name="PageA.aspx Type="Ghostable" />  
    <File Url="Page01.aspx" Name="PageB.aspx Type="Ghostable" />  
    <File Url="Page01.aspx" Name="PageC.aspx Type="Ghostable" />  
  </Module> 
</Elements>

Safe Mode Processing

It's important to understand that all customized site pages are parsed and processed in a special mode known as safe mode. The primary motivation for safe mode involves the fact that standard users can modify the contents of site pages. In other words, a user (such as a site owner) possessing no administrator privileges within the farm can make any modifications to a page within a site. Consider a scenario in a large farm in which a site administrator attempts to mount an attack on the Web server by writing C# code within a customized site page inside an in-line script block. Safe mode prevents this type of attack by disallowing in-line script in any customized source.

Examine the code in the page template named Page02.aspx. It contains a simple in-line script to write a message back to the browser.

<%@ Page Language="C#" MasterPageFile="~masterurl/default.master"  
    meta:progid="SharePoint.WebPartPage.Document"  %> 

<asp:Content ID="main"   
             ContentPlaceHolderID="PlaceHolderMain"> 
  <h3>Page 2</h3> 
  <% Response.Write("Hello world from server-side script!"); %> 
</asp:Content>

Note that this page and the in-line script run just fine as long as the page remains uncustomized in a ghosted state. Remember that Windows SharePoint Services compiles a ghosted page into an assembly DLL for processing. However, as soon as a user modifies any aspect of this page with SharePoint Designer and moves the site page into an unghosted state, Windows SharePoint Services then begins to use safe mode to process it. Because the page contains in-line script, Windows SharePoint Services refuses to process it in safe mode and generates the error message shown in Figure 3-4.

Figure 3-4. Customized pages run in safe mode and cannot contain in-line script

You obviously don't want your users to experience error messages like the one shown in Figure 3-4. For this reason, you should avoid adding in-line script to page templates. Following this guideline helps to eliminate scenarios in which pages mysteriously stop working after they are edited by users with SharePoint Designer.

In rare cases, you might decide to turn down or turn off the protection afforded by safe mode. In this situation, you can add an entry to the web.config file of the hosting Web application to instruct Windows SharePoint Services to change the behavior of safe mode processing. For example, assume that you want to allow in-line scripts for site pages inside the SitePages folder in a site at the path of /sites/Sales. You can accomplish this by adding the following PageParserPath element within the SharePoint section of the web.config file.

<SharePoint> 
  <SafeMode ... > 
    <PageParserPaths> 
      <PageParserPath  
          VirtualPath="/sites/Sales/SitePages/*"  
          IncludeSubFolders="true"  
          CompilationMode="Always"  
          AllowServerSideScript="true" /> 
    </PageParserPaths> 
  </SafeMode> 
</SharePoint>

If you examine the PageParserPath element, you see that the VirtualPath attribute has a Web application-relative path followed by an asterisk, which includes every site page in that particular folder. Also note that the CompilationMode attribute has a value of Always and the AllowServerSideScript attribute has a value of true. This instructs the safe mode parser to compile all site pages into assembly DLLs and allow in-line script.

Note that a page must be compiled into an assembly DLL to support in-line script, which means that it is not valid to assign a value of Never to the CompilationMode attribute while assigning a value of true to the AllowServerSideScript attribute. Also note that you can assign a value of Auto instead of a value of Always to the CompilationMode attribute. This has the effect of compiling only pages that contain in-line script. When the CompilationMode attribute has a value of Auto, pages without inline script are still run in no-compile mode.

It is possible to enable inline script for all site pages within a Web application by configuring the VirtualPath attribute with a value of /* and then setting the CompilationMode attribute to a value of Always or Auto. However, two significant factors should motivate you not to do this.

The first factor is security. By enabling inline script for all site pages within a Web application, you open the door to attacks on the Web server because any user who has the ability to customize a page can freely write managed code that executes on the Web server.

The second factor pertains to scalability. Earlier in this chapter, I discussed how no-compile pages are more scalable than compiled pages in a large Web application. Windows SharePoint Services experiences scaling problems if your Web application attempts to compile and load thousands of assembly DLLs for all of your customized pages. At the very least, you should prefer a CompilationMode setting of Auto instead of Always so that only pages that actually contain script are compiled into assembly DLLs, whereas those pages that do not contain script continue to be parsed and processed in no-compile mode.

Safe Controls

Safe mode processing goes a step beyond protecting against in-line script by also considering what controls a user might place on a customized page. For example, imagine a scenario in which a site administrator tries to mount an attack by adding a server-side control to a site page and parameterizing it in a certain way. Safe mode allows the farm administrator to determine which controls can be used in pages that are processed in safe mode.

Customized pages can only contain server-side controls that are explicitly registered as safe controls. Registering a control as a safe control is accomplished by adding a SafeControl entry into the web.config file for the hosting Web application.

<SafeControls> 
  <SafeControl  
    Assembly="Microsoft.SharePoint, …"  
    Namespace="Microsoft.SharePoint.WebControls"  
    TypeName="*"  
    AllowRemoteDesigner="True" /> 
</SafeControls>

Note that the standard web.config file for a Web application automatically includes SafeControl entries for the standard server-side controls and Web Parts included with ASP.NET and Windows SharePoint Services. In the next section, you will learn how to add a SafeControl entry that is required to place a custom server-side control on a customized page. In Chapter 4, "Web Parts," we will revisit the topic of safe controls and discuss how they pertain to custom Web Part deployment.

Note that a PageParserPath element, in addition to allowing in-line script, can also override the default safe mode behavior and allow for server-side controls that are explicitly registered as safe. For example, you can allow the users of a particular site to add any server-side controls to customized pages by using the following entry within the web.config file.

<SharePoint> 
  <SafeMode ... > 
    <PageParserPaths> 
      <PageParserPath  
          VirtualPath="/sites/Sales/*"  
          AllowUnsafeControls="true" /> 
    </PageParserPaths> 
  </SafeMode> 
</SharePoint>

Note that using this option affects only which server-side controls can be added to a page when customizing a page with a tool, such as SharePoint Designer. This configuration option does not extend to control instances when users are adding Web Parts to Web Part zones on a page through the browser. Assembly DLLs containing Web Parts must always be explicitly registered by using SafeControl elements for users to be able to place them inside Web Part zones.

Although you have just learned several ways to disable safe mode or lessen its effects, you should remember to proceed here with extreme caution. It's usually best to leave safe mode with its default behavior. Windows SharePoint Services was engineered with safe mode processing to protect the farm from attacks and allow Windows SharePoint Services to scale out the way it was designed in large farm environments.

Designing Site Pages by Using Controls

We have dealt with the nuts and bolts of site pages, page templates, and safe mode processing. It is now time to take a step back and discuss the development of page templates from a design perspective. In particular, we will discuss how to construct page templates by adding server-side controls to obtain the functionality and user interface components that you need.

In the ASP.NET programming model, two categories of server-side controls exist: custom controls and user controls, both of which are useful when designing page templates. Custom controls are more lightweight and must be compiled into an assembly DLL before being deployed to the front-end Web server. User controls are more productive because you can use the visual designer supplied by Visual Studio to create them.

After an overview of the use of custom controls and user controls, this section will address the creation of Web Part pages by creating a page template with one or more Web Part zones. During this discussion, we will explore the Windows SharePoint Services infrastructure that makes browser-based user customization through Web Part possible.

Constructing Pages with Custom Controls

Let's begin with a simple example of a custom control. A server-side control in ASP.NET is defined as a class that inherits from the Control class. However, developers often choose to create custom controls by deriving from a richer class named WebControl, which inherits from the Control class. The following code example illustrates a custom control class that inherits from WebControl and implements "Hello World" functionality by overriding the RenderContents method and adding some simple code that programs against the Windows SharePoint Services object model.

using System.Web.UI; 
using System.Web.UI.WebControls; 
using Microsoft.SharePoint; 
 
namespace CustomSitePages { 
  public class CustomControl1 : WebControl  { 
    protected override void RenderContents(HtmlTextWriter output)  { 
      SPWeb site = SPContext.Current.Web; 
      output.Write("Current Site: " + site.Title); 
      output.Write("<br/>"); 
      output.Write("Current Site ID: " + site.ID.ToString()); 
    } 
  } 
}

You have two choices as to where to deploy an assembly DLL that contains a custom control when you want to use it in a site page. First, you can compile the assembly DLL with a strong name and install it in the global assembly cache. Alternatively, you can deploy the assembly DLL by placing it inside the root directory of the hosting Web application inside a nested directory named bin. Note that when you plan to deploy the assembly DLL with custom controls in the bin directory, you have the option of compiling it with or without a strong name.

Once the assembly DLL with the custom control is properly deployed, you can then reference it within a page template by using the ASP.NET @ Register directive. The following code example displays a page template that uses the custom control shown previously.

<%@ Page MasterPageFile="~masterurl/default.master"  
    meta:progid="SharePoint.WebPartPage.Document" %> 

<%@ Register Assembly="CustomSitePages, ... " 
    Namespace="CustomSitePages" TagPrefix="CustomSitePages" %> 

<asp:Content ID="main"  
     ContentPlaceHolderId="PlaceHolderMain"  
     > 

<h3>A custom control example</h3> 

<CustomSitePages:CustomControl1 ID="cc1"  /> 

</asp:Content>

As you can see, adding a @ Register directive is just like adding an assembly reference to a project in Visual Studio because it makes the public components inside the target assembly available for use within the page. However, the@ Register directive also defines a TagPrefix attribute with a value of CustomSitePages. This TagPrefix value is then used to instantiate instances of the control within the page.

<CustomSitePages:CustomControl1 ID="cc1"  />

When you navigate to Page02.aspx, you should see that the page renders the output of the control so that it is visible to the user. However, this works only while the hosting page remains in a ghosted state. Remember that customized pages allow only for controls that are registered as safe controls. If a user customizes Page02.aspx with SharePoint Designer, the page begins to execute in safe mode, and the presence of a control that is not registered as a safe control results in the error message shown in Figure 3-5.

Figure 3-5. Safe mode processing does not allow controls that are not registered as safe controls

To fix this problem, you must add a custom SafeControl entry to the hosting Web application's web.config file. You can accomplish this by adding a SafeControl entry that looks like the following.

<SafeControl  
  Assembly="CustomSitePages, ..."  
  Namespace="CustomSitePages"  
  TypeName="CustomControl1"  
/>

Note that when you add a SafeControl entry, you can define the type name for the control class explicitly as shown here, or you can alternatively use a TypeName value of * to register all of the server-side controls and Web Parts within the specific namespace that reside in the target assembly DLL.

Constructing Pages with User Controls

User controls provide a more productive alternative to custom controls. They are easier to develop because they are deployed on the front-end Web server as simple text files with an .ascx extension. The ASP.NET runtime provides the functionality to parse these .ascx files at run time and compile them into assembly DLLs just as it does for .aspx files.

Let's examine the source file for a simple user control. The following example of an .ascx file creates a simple user interface with a command button and a label and adds in an event handler to provide the classic "Hello World" functionality.

<%@ Control Language="C#" %> 

<script > 
  protected void cmdButton1_Click(object sender, EventArgs e) { 
    lblStatus.Text = "Hello, World"; 
  } 
</script> 

<asp:Button ID="cmdAddCustomer"  Text="Add Customer"  
            OnClick="cmdAddCustomer_Click" /> 
<br/> 
<asp:Label ID="lblStatus"  Text="" />

As you can see, it's fairly easy to get started with user controls. You can even use Notepad.exe to create and modify simple .ascx files. However, you will likely prefer using Visual Studio to develop .ascx files because it provides a user control editor. This visual editor provides significantly higher levels of productivity because you can use standard Visual Studio design tools, such as the control toolbox and property sheet shown in Figure 3-6.

Figure 3-6. Visual Studio provides a productivity-oriented designer for developing user controls

It is important to understand that Windows SharePoint Services does not support user customization of user controls. User controls are always loaded from the file system of the front-end Web server and compiled into assembly DLLs. Furthermore, user controls can be copied to the front-end Web server only by someone with farm-level administrative privileges. For these reasons, you can assume that you can always write in-line code in an .ascx file.

Assume that you want to write in-line code in a user control that programs against the Windows SharePoint Services object model. That's easy. All you have to do is add an @ Assembly directive to the top of the .ascx file that references the four-part assembly name of Microsoft.SharePoint.dll. You can also add an @ Import directive to import one or more namespaces to make your code more concise.

<%@ Control Language="C#" %> 
<%@ Assembly Name="Microsoft.SharePoint, ..." %> 
<%@ Import Namespace="Microsoft.SharePoint" %> 

<script > 
  protected override void OnLoad(EventArgs e)  { 
    SPWeb site = SPContext.Current.Web; 
    lblDisplay.Text = "Current Site: " + site.Url; 
  } 
</script> 

<asp:Label ID="lblDisplay"  />

In Chapter 2, you were introduced to the virtual _layouts directory and learned that this was the proper place to deploy application pages. Windows SharePoint Services provides a similar virtual directory for deploying user controls. Inside the TEMPLATE directory resides a nested directory named CONTROLTEMPLATES. This directory contains many different user controls that are deployed as part of the standard Windows SharePoint Services installation.

The CONTROLTEMPLATES directory is also a place where you should deploy custom user control files. However, it's a good practice to create your own nested directory inside the CONTROLTEMPLATES directory to avoid potential file name conflicts. The CustomSitePages project creates a company-specific inner directory named Litware and copies its user controls into that directory. Each custom user control is copied to a physical path that looks like the following.

TEMPLATES/CONTROLTEMPLATES/Litware/UserControl1.ascx

Each Web application is configured with a virtual directory named _controltemplates that points to the physical CONTROLTEMPLATES directory. This makes it possible to reference any user control file by using a standard path relative to the hosting Web application. For example, one of the user controls from the CustomSitePages project can be referenced by using a virtual path that looks like the following.

~/_controltemplates/Litware/UserControl1.ascx

When deploying user controls, it's important to remember that they follow the same rules with respect to safe mode processing. If you want to place a user control on a site page that might be customized, the .ascx file must be registered as a safe control in the web.config file of the hosting Web application. Fortunately, you don’t have to worry about this if you deploy your custom user controls inside the virtual _controltemplates directory because the standard web.config file for a Web application already contains the following SafeControl entry.

<SafeControl  
  Src="~/_controltemplates/*"  
  IncludeSubFolders="True"  
  Safe="True"  
  AllowRemoteDesigner="True"  
/>

Now that you have seen how to create and properly deploy a user control, the final step is constructing a page template that references the .ascx file and creates an instance. Similar to constructing pages with custom controls, this is accomplished by placing a @ Register directive on the page template. However, the process is different with user controls because the @ Register directive requires an src attribute that points to the virtual path of the target .ascx file.

<%@ Page MasterPageFile="~masterurl/default.master"   
    meta:progid="SharePoint.WebPartPage.Document"  %> 

<%@ Register TagPrefix="luc" TagName="UserControl1"    
    src="~/_controltemplates/Litware/UserControl1.ascx" %> 

<asp:Content  ContentPlaceHolderID="PlaceHolderMain"> 
  <luc:UserControl1 ID="id1"  />   

</asp:Content>

You have now seen all of the steps involved with constructing site pages using custom controls and user controls. This provides you with a strategy to create components that are reusable and versionable, and also provides you with a technique for adding whatever custom code you want to pages that are running in safe mode.

Note that all techniques shown here for constructing pages with custom controls and user controls can be used within custom application pages in the same fashion as they are in site pages. The one major difference is that application pages do not support user customization and never run in safe mode. If you want to create a custom control or user control that is used only on application pages, you do not need to worry about registering controls as safe controls in the web.config file.

Designing Web Part Pages

Web Parts provide a valuable dimension to Windows SharePoint Services. In particular, Web Parts make it possible for a site owner to customize a site page with changes that are seen by all users. Web Parts go even further to allow individual users to add personalization changes that are seen only by them. Windows SharePoint Services provides the underlying mechanisms to track all of this customization and personalization inside the content database along with all of the other site-related data.

Before diving into the details of how Web Part pages work, two important aspects of their architecture must be noted. First, support for customizing and personalizing Web Parts is available in site pages but not in application pages, thus giving site pages a clear design advantage over application pages.

Second, adding and customizing Web Parts does not require customizing the Web Part pages that host them. A Web Part page defines Web Part zones but does not define what goes inside these zones. Instead, all of the data for tracking Web Part instances and their customization and personalization data are kept in separate tables inside the content database. This means that a Web Part page can remain in a ghosted state even though users are continually adding, customizing, and personalizing the Web Parts within its zone.

Web Part pages in a Windows SharePoint Services 3.0 site are built on top of the new Web Part infrastructure introduced with ASP.NET 2.0. To create a Web Part page in an ASP.NET 2.0 application, you must create an .aspx page that contains exactly one instance of a control named WebPartManager and one or more WebPartZone controls. The WebPartManager is responsible for managing the lifetime of Web Part instances as well as serializing Web Part–related data so that it can be stored and retrieved from the tables in the ASP.NET services database.

The Web Part infrastructure of Windows SharePoint Services 3.0 does not use the standard WebPartManager control from ASP.NET. Instead, Windows SharePoint Services relies on a specialized control named SPWebPartManager that derives from the ASP.NET 2.0 WebPartManager control. The SPWebPartManager control overrides the standard behavior of the WebPartManager control to persist Web Part data inside the Windows SharePoint Services content database instead of inside the ASP.NET services database.

In most cases, you don't have to worry about dealing with the SPWebPartManager control directly because the one and only required instance of the SPWebPartManager is already defined in the standard default.master page. When you create a site page that links to default.master, the SPWebPartManager control is automatically added to the page. Therefore, you simply need to add one or more WebPartZone controls.

Two things must be done when creating a page template for a Web Part page. The first is to inherit from the WebPartPage class that is defined inside the Microsoft.SharePoint.dll assembly. The second is to add one or more WebPartZone controls. Note that you must use the WebPartZone control defined by the Windows SharePoint Services team and not the one of the same name defined by the ASP.NET team.

To add WebPartZone controls to a page template, you must add a @ Register directive that imports all of the controls from the Microsoft.SharePoint.dll assembly defined in the Microsoft.SharePoint.WebPartPages namespace as shown in the following page template definition.

<%@ Page MasterPageFile="~masterurl/default.master"  
    Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, 
              Microsoft.SharePoint, [full 4-part assembly name]" 
    meta:progid="SharePoint.WebPartPage.Document"   %> 

<%@ Register Tagprefix="WebPartPages"  
    Namespace="Microsoft.SharePoint.WebPartPages"  
    Assembly="Microsoft.SharePoint, ..." %> 

<asp:Content ID="main"  ContentPlaceHolderID="PlaceHolderMain" > 

<h3>Custom Web Part page</h3> 

<table width="100%"> 
  <tr> 
    <td valign="top" style="width:50%"> 
        <WebPartPages:WebPartZone ID="LeftZone"   
                                  FrameType="TitleBarOnly"  
                                  Title="Left Web Part zone" />         
    </td> 
    <td valign="top" style="width:50%"> 
        <WebPartPages:WebPartZone ID="RightZone"   
                                  FrameType="TitleBarOnly"  
                                  Title="Right Web Part zone" />         
    </td> 
  </tr> 
</table> 
</asp:Content>

It is easier to create pages that host Web Parts in the Windows SharePoint Services framework than in the ASP.NET Framework. For example, when you design an ASP.NET application that involves Web Parts, you are required to add logic to each page that interacts with the WebPartManager control to manage the display mode. It is also necessary to explicitly add controls, such as Editor Zones and Catalog Zones, to the page so that users can customize existing Web Parts as well as add new Web Parts.

Fortunately, you don't need to worry about managing the display mode or adding Editor Zones and Catalog Zones when creating Web Part pages for Windows SharePoint Services. When you create a Web Part page that inherits from the WebPartPage class, all of this work is done for you behind the scenes. The Site Actions menu automatically provides the Edit Page command that allows the user to enter a mode for adding and customizing Web Parts.

In the CustomSitePages project, three different site pages are provisioned from the page template named WebPartPage.aspx. If you navigate to the first site page named WebPartPage01.aspx and select the Edit Page command from the Site Actions menu, you will see that there are two empty zones, as shown in Figure 3-7. At this point, you can use basic Windows SharePoint Services support to add a new Web Part instance as you would to any other Web Part page, such as default.aspx.

Figure 3-7. A Web Part page is designed with one or more Web Part zones

When you provision a Web Part page from a page template, it initially contains no Web Parts in any of its Web Part zones. While you could rely on users manually adding Web Parts to your pages, it is more convenient and reliable for you to use a technique in which you prepopulate Web Part zones with whatever Web Parts your business solution requires.

There are two common techniques for adding a Web Part instance to a Web Part zone. The first technique involves a declarative approach used inside a feature in which you define an AllUsersWebPart element inside a File element. The following example demonstrates the File element that is used in the CustomSitePages project to provision the Web Part page named WebPartPage02.aspx.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 
  <Module Path="PageTemplates" Url="SitePages" > 
    <File Url="WebPartPage.aspx" Name="WebPartPage02.aspx" Type="Ghostable" > 
      <!-- Add a Web Part to left zone --> 
      < ="LeftZone" WebPartOrder="0"> 
        <![CDATA[  
          <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2"  
            xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image"> 
            <Assembly>Microsoft.SharePoint, ...</Assembly> 
    <TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName> 
            <FrameType>None</FrameType> 
            <Title>Watch My Gears Run</Title> 
           <iwp:ImageLink>/_layouts/images/GEARS_AN.GIF</iwp:ImageLink>
          </WebPart>                            ]]> 
      </AllUsersWebPart> 
    </File> 
  </Module> 
</Elements>

As you can see, a File element can contain an inner AllUsersWebPart element that references a target Web Part zone and includes serialized data for the Web Part instance to be created. We will revisit the inner WebPart element in more detail in Chapter 4 when we discuss Web Part description files.

The second technique for adding a Web Part instance to a Web Part page involves writing code against the Windows SharePoint Services object model. An example of this type of code is supplied in the FeatureActivated event handler for the CustomSitePages project. The code obtains a reference to the SPFile object associated with WebPartPage03.aspx and uses an SPLimitedWebPartManager object to add a new Web Part instance to a particular target zone.

public override void FeatureActivated( 
                       SPFeatureReceiverProperties properties) { 
  // acquire objects for site, page and limited Web Part Manager 
  SPWeb site = (SPWeb)properties.Feature.Parent; 
  SPFile page = site.GetFile("SitePages/WebPartPage03.aspx"); 
  SPLimitedWebPartManager mgr; 
  mgr = page.GetLimitedWebPartManager(PersonalizationScope.Shared); 

  // add Web Part to Right Zone 
  ImageWebPart wp1 = new ImageWebPart(); 
  wp1.ChromeType = PartChromeType.None; 
  wp1.ImageLink = @"/_layouts/images/IPVW.GIF"; 
  mgr.AddWebPart(wp1, "RightZone", 0); 
}

The advantage to using the first technique is that Web Parts can be added to pages with declarative logic in the same place where the actual page is being provisioned. The advantage of using the second approach involving code is that it is more flexible. While you can execute code that adds a Web Part instance during feature activation, you can also execute the same code long after the feature is activated. For example, imagine a scenario in which you would like to write the code required to enumerate through every site within a farm to add a special Web Part to a target zone on every home page. The Windows SharePoint Services object model makes it possible to automate this type of administrative task with Web Part pages.

Next Part: Chapter 3: Pages and Design (Part 2 of 2)