Chapter 2: SharePoint Architecture (Part 2 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.

Previous Part: Chapter 2: SharePoint Architecture (Part 1 of 2)

Download the sample code that accompanies the book

Contents

  • Creating Custom Application Pages

  • Summary

Creating Custom Application Pages

This chapter is accompanied by a sample Visual Studio project named CustomApplicationPages. This project contains several different examples of application pages such as Hello.aspx, ApplicationPage1.aspx, ApplicationPage2.aspx, and ApplicationPage3.aspx. This Visual Studio project also has a post-build event that runs a batch file named install.bat, which copies the application pages to where they need to be deployed inside the \LAYOUTS directory. The install.bat file also installs a feature named CustomApplicationPages, which adds menu items to the Site Actions menu of the current site and allows users to navigate the project’s application pages.

Once you open the CustomApplicationPages project, you should build it. This will run the install.bat file. After that point, you should be able to go to any site and activate the site-level feature named CustomApplicationPages, which will then allow you to follow along with the examples throughout the remainder of this chapter.

Notice that while it is possible to deploy your application pages directly inside the \LAYOUTS directory, this can cause problems due to potential file name conflicts between your application pages and those created by Microsoft and other companies. It is a best practice to deploy your application pages inside a company-specific or project-specific directory that is nested within the \LAYOUTS directory. The application pages in the sample project discussed here are deployed within a project-specific directory located at the path \LAYOUTS\CustomApplicationPages.

As a rule, application pages should derive from a base class in the Microsoft.SharePoint assembly named LayoutsPageBase. For example, assume that you want to create a custom application page to display some information about the current site. In our first example, we will work with an application page named Hello.aspx, which does exactly that.

Application pages are usually created as content pages that link to the master page named application.master that resides in the \LAYOUTS directory. This master page file can be referenced in a custom application page using a virtual path of ~/_layouts/application.master. The definition for the application page named Hello.aspx shown in Listing 2-1 shows the starting point for creating your own application pages.

Listing 2-1. A custom application page can contain HTML layout and in-line code

<%@ Assembly Name="Microsoft.SharePoint, [full 4-part assembly name]"%>  
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master"  
         Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase"  %> 

<%@ Import Namespace="Microsoft.SharePoint" %> 

<script > 
  protected override void OnLoad(EventArgs e) { 
    SPWeb site = this.Web; 
    lblSiteTitle.Text = site.Title; 
    lblSiteID.Text = site.ID.ToString().ToUpper(); 
  } 
</script> 

<asp:Content ID="Main" contentplaceholderid="PlaceHolderMain" > 
  <table border="1" cellpadding="4" cellspacing="0" style="font-size:12"> 
    <tr> 
      <td>Site Title:</td> 
      <td><asp:Label ID="lblSiteTitle"  /></td> 
    </tr> 
    <tr> 
      <td>Site ID:</td> 
      <td><asp:Label ID="lblSiteID"  /></td> 
    </tr> 
  </table>        
</asp:Content> 

<asp:Content ID="PageTitle"  
  contentplaceholderid="PlaceHolderPageTitle" > 
  Hello World 
</asp:Content> 

<asp:Content ID="PageTitleInTitleArea"  
  contentplaceholderid="PlaceHolderPageTitleInTitleArea" > 
  The Quintessential 'Hello World' Application Page 
</asp:Content>

Notice that the application page example shown in Listing 2-1 adds three Content tags to add HTML content to the resulting page. In particular, this page replaces placeholders defined inside application.master named PlaceHolderMain, PlaceHolderPageTitle, and PlaceHolderPageTitleInTitleArea. However, these are just three of the many different placeholders defined inside application.master that you can choose to replace.

Also notice that the version of Hello.aspx shown in Listing 2-1 has a script block at the top containing code that programs against the Windows SharePoint Services object model. When you are programming a page like this within Visual Studio 2005, you are able to benefit from conveniences such as color-coding and IntelliSense. However, you must be sure to add the correct @Assembly directive to the top of the page. You should modify the @Assembly directive shown in Listing 2-1 with the assembly information for the Microsoft.SharePoint assembly shown here.

Microsoft.SharePoint,  
Version=12.0.0.0,  
Culture=neutral,  
PublicKeyToken=71E9BCE111E9429C

We have added line breaks here to make this information more readable. However, you should make sure that you add all of the assembly information in a single line whenever you add an @Assembly directive to an .aspx file.

Application pages are handy because they provide quick and easy access to the Windows SharePoint Services object. Once you have created an application page and provided an overridden implementation of the OnLoad method, as shown in Listing 2-1, you can obtain entry points into the Windows SharePoint Services object model in a site-specific context using the following code, which uses properties exposed by underlying LayoutsPageBase base class.

SPSite siteCollection = this.Site; 
SPWeb site = this.Web;

The ability to obtain site-relative as well as site collection–relative context makes writing application pages far more powerful. An application page can behave differently depending on which site you go through to access it. When you navigate to an application page through the context of one site, it will typically appear and behave differently when you navigate to it through the context of another site.

Our example of Hello.aspx uses Windows SharePoint Services object model code in the OnLoad method to obtain information about the current site and then to display that information on labels that are defined on the placeholder named PlaceHolderMain.

protected override void OnLoad(EventArgs e) { 
    SPWeb site = this.Web; 
    lblSiteTitle.Text = site.Title; 
    lblSiteID.Text = site.ID.ToString().ToUpper(); 
} 

When you access Hello.aspx through the context of a particular site, it displays relative information about the current site, as shown in Figure 2-9.

Figure 2-9. A custom application page can easily be programmed against the Windows SharePoint Services object model

The Code-Behind Approach in Application Pages

While you have just seen it is possible and fairly easy to add in-line code inside the .aspx file for a custom application page, you can also use a different approach in which your code is maintained in a code-behind style supported by ASP.NET. All the example application pages in the CustomApplicationPages sample project use this approach, with the exception of the one application page you have already seen named Hello.aspx. You might prefer this code-behind approach because it provides a separation between your code and the user interface layout details. The code-behind approach can also improve your design time experience when writing the code for application pages.

Let's start by examining ApplicationPage1.aspx, which has two server control tags and output that is identical to Hello.aspx. The big difference is that the code for ApplicationPage1.aspx is maintained in a source file named ApplicationPage1.cs, which is compiled into an assembly named CustomApplicationPages.dll that it installed in the Global Assembly Cache (GAC). If you examine the definition for ApplicationPage1.aspx in Listing 2-2, you can see that it contains the same HTML markup and control tags as Hello.aspx, but the code has been removed.

Listing 2-2. A custom application page can reference a code-behind base class

<%@ Assembly Name="Microsoft.SharePoint, [full 4-part assembly name]" %> 
<%@ Assembly Name="CustomApplicationPages, [full 4-part assembly name]" %> 
 
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master"  
  Inherits="CustomApplicationPages.ApplicationPage1" 
  EnableViewState="false" EnableViewStateMac="false" %> 

<asp:Content ID="Main" contentplaceholderid="PlaceHolderMain" > 
  <table border="1" cellpadding="4" cellspacing="0" style="font-size:12"> 
    <tr> 
      <td>Site Title:</td> 
      <td><asp:Label ID="lblSiteTitle"  /></td> 
    </tr> 
    <tr> 
      <td>Site ID:</td> 
      <td><asp:Label ID="lblSiteID"  /></td> 
    </tr> 
  </table>        
</asp:Content> 

<asp:Content ID="PageTitle"  
             contentplaceholderid="PlaceHolderPageTitle" > 
  Hello World 
</asp:Content> 

<asp:Content ID="PageTitleInTitleArea"  
             contentplaceholderid="PlaceHolderPageTitleInTitleArea" > 
  Application Page 1: 'Hello World' with code behind 
</asp:Content>

There are two other important differences between Hello.aspx and , ApplicationPage1.aspx contains a second assembly directive which references the output assembly for the current project named CustomApplicationPages.dll. Second, ApplicationPage1.aspx does not inherit directly from LayoutsPageBase; instead it inherits from a code-behind class named ApplicationPage1, which is defined in the namespace CustomApplicationPages. Now let's examine Listing 2-3, which shows the definition for this code-behind class inside the source file ApplicationPage1.cs, which has been written to inherit from LayoutsPageBase.

Listing 2-3. A code-behind class for an application page should inherit from LayoutsPageBase

using System; 
using System.Web; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using Microsoft.SharePoint; 
using Microsoft.SharePoint.WebControls; 

namespace CustomApplicationPages { 

public class ApplicationPage1 : LayoutsPageBase { 

    // Add control fields to match controls tags on .aspx page 
    protected Label lblSiteTitle; 
    protected Label lblSiteID; 

    protected override void OnLoad(EventArgs e) { 

        // Get current site and web 
        SPSite siteCollection = this.Site; 
        SPWeb site = this.Web; 

        // Program against controls on .aspx page 
        lblSiteTitle.Text = site.Title; 
        lblSiteID.Text = site.ID.ToString().ToUpper(); 
    } 
  } 
}

When you have an .aspx file and a code-behind class, there is a handy technique in ASP.NET programming where the code-behind class defines a control field using the same name as a control instance in the .aspx page. When the ASP.NET runtime compiles the .aspx file, it adds support to create the control instance and assign a reference to the control field in the code-behind class. This makes it possible for methods within the code-behind class to access the control instances defined inside the .aspx file. For example, the ApplicationPage1 class defines control fields named lblSiteTitle and lblSiteID to match the control tags defined in ApplicationPage1.aspx. This makes it possible for code within the OnLoad event handler method inside ApplicationPage1.cs to access these controls through the control fields named lblSiteTitle and lblSiteID.

Debugging Windows SharePoint Services Components

Under normal conditions, Windows SharePoint Services provides error messages intended for end users in a production environment. This means that Windows SharePoint Services doesn't automatically provide rich diagnostic information or helpful error messages to assist you when you are debugging your code. While you are developing Windows SharePoint Services components such as custom application pages, you must modify the web.config file for the current Web application to enable debugging support and error messages that contain stack traces. Here's a fragment of the web.config file that shows the three important attributes that have been changed from their default values to enable rich debugging support.

<configuration> 
  <SharePoint> 
    < ="true" /> 
  </SharePoint> 
  <system.web> 
    <customErrors mode="Off" /> 
    <compilation debug="true" /> 
  </system.web> 
</configuration> 

While it is essential to modify the web.config file as shown here to enable debugging support, you will also find it occasionally necessary to return the web.config file to its original state. For example, you might create an application page that throws an exception or an event handler that cancels a user's actions. By returning the web.config file to its original state, you can see how your code will actually behave in a production environment. Therefore, you should become comfortable with the act of changing the web.config file back and forth between debugging mode and standard end user mode.

Now that you have seen how to create an application page, let's complement it by adding a feature that adds a menu item to the Site Actions menu. This menu item will be created to allow the user to navigate to ApplicationPage1.aspx. In the last chapter, we demonstrated a technique for creating a custom feature with a CustomAction element. We will now create another CustomAction with a slight variation. Take a look at the following CustomAction element.

<!-- Add command to Site Actions Dropdown --> 
<CustomAction Id="SiteActionsToolbar" 
  GroupId="SiteActions" 
  Location="Microsoft.SharePoint.StandardMenu" 
  Sequence="2001" 
  Title="Application Page 1" 
  Description="Check out some typical site properties" 
  ImageUrl="/_layouts/images/DECISION.GIF"> 
    <UrlAction Url="~site/_layouts/CustomApplicationPages/ApplicationPage1.aspx"/> 
</CustomAction>

You should see that this CustomAction element is almost identical to the one we demonstrated in chapter 1. The difference rests in the string used for the URL attribute of the inner UrlAction element. The URL attribute in this example begins with ~site. This is a dynamic token that is replaced by Windows SharePoint Services with the actual URL to the current site. By using this technique, you can provide a menu item within the Site Actions menu, such as the one shown in Figure 2-10, that allows the user to navigate to your custom application pages with the correct site-relative context.

Figure 2-10. A menu item created with a CustomAction element makes it easy for the user to navigate to a custom application page

When creating a CustomAction element, you can configure the URL attribute of the inner UrlAction element by using the ~site token, as you have just seen. You should notice that Windows SharePoint Services also supports the ~sitecollection token in cases in which your application page should always be executed within a URL associated with the current site collection and its top-level site.

Creating an Application Page with the SPGridView Control

ApplicationPage2.aspx demonstrates the technique of employing a handy server-side control created by the Windows SharePoint Services team, named SPGridView. SPGridView is an enhanced version of the ASP.NET GridView control that integrates with the Windows SharePoint Services Cascading Style Sheet (CSS) files so that it picks up the same look and feel as its surrounding environment.

The SPGridView control is defined inside the Microsoft.SharePoint assembly in the Microsoft.SharePoint.WebControls namespace. In order to use this control inside an application page, you must add an @Assembly directive referencing the Microsoft.SharePoint assembly; in addition, you must add an @Register directive to import the control’s namespace and define a Tagprefix attribute that will be used when creating a control tag. Here's an example of the @Register directive that is used in ApplicationPage2.aspx.

<%@ Register Tagprefix="SharePoint" 
    Namespace="Microsoft.SharePoint.WebControls" 
    Assembly="Microsoft.SharePoint, [full 4-part assembly name]" %>

As you can see, this @Register directive imports the Microsoft.SharePoint.WebControls namespace with a Tagprefix value of SharePoint. That makes it possible to create an instance of the SPGridView control using a control tag that looks like this.

<SharePoint:SPGridView   
                       ID="grdPropertyValues"  
                       AutoGenerateColumns="false" 
                       RowStyle-BackColor="#DDDDDD" 
                       AlternatingRowStyle-BackColor="#EEEEEE" />

Take a moment and examine the complete application page definition in the source file named ApplicationPage2.aspx. After you have done that, open the source file named ApplicationPage2.cs and examine the code-behind class for this application page. You can see that the class named ApplicationPage2 works together with a custom helper class named PropertyCollectionBinder to create an object filled with information about the current site and site collection. The DataTable object is being used in this example because its contents can be used to populate SPGridView by using standard ASP.NET data binding techniques.

Restricting Application Pages to Site Administrators

The application pages and supporting menu items in the Site Actions menu shown so far have been created in a manner so that they are accessible to all users of a site. However, that isn't always desirable. Application pages are often designed to provide information and functionality that should only be accessible to site administrators. We will discuss how to do this by examining our next sample application page, named ApplicationPage3.aspx.

Let's start by revisiting the creation of a CustomAction element within a feature. You can add an attribute named RequireSiteAdministrator and assign it a value of true so that the menu item only displays to those users who are also site administrators.

<!-- Add command to Site Actions Dropdown --> 
<CustomAction Id="SiteActionsToolbar" 
  GroupId="SiteActions" 
  Location="Microsoft.SharePoint.StandardMenu" 
  Sequence="1003" 
  Title="Application Page 3" 
  Description="Admin-only Application page" 
  RequireSiteAdministrator="True" 
  ImageUrl="/_layouts/images/DECISION.GIF"> 
    <UrlAction Url="~site/_layouts/CustomApplicationPages/ApplicationPage3.aspx"/> 
</CustomAction>

While this provides a good start by hiding the menu item from users who are not site administrators, it doesn't provide a truly secure solution. The application page itself will still be accessible to any user who knows the URL. When you want to lock down an application page in a secure manner, you can accomplish this by overriding the RequireSiteAdministrator property of the LayoutsPageBase base class as shown in the following code.

public class ApplicationPage3 : LayoutsPageBase { 

  protected override bool RequireSiteAdministrator { 
    get { return true; } 
  } 

  protected override void OnLoad(EventArgs e) { 
    // Your code goes here 
  } 
}

Once you add this code to an application page, you truly make it secure. When a user who is not a site administrator tries to navigate to this application page, the user is redirected to the standard Windows SharePoint Services Access Denied page.

Adding a Custom Menu Item to the ECB Menu

We will now take the idea of creating a custom application page even further by examining one that displays information about a particular document in a document library. In our example, we will look at the application page named ApplicationPage4.aspx. We begin by creating a new CustomAction element. However, this CustomAction element does not add a menu item in the Site Actions menu. Instead, it is designed to add a new menu item to the ECB menu for each document within a document library. Examine the following XML fragment that defines a CustomAction.

<!-- Per Item Dropdown (ECB) Link --> 
<CustomAction Id="ApplicationPage4.ECBItemMenu" 
  RegistrationType="List"  
  RegistrationId="101" 
  ImageUrl="/_layouts/images/GORTL.GIF" 
  Location="EditControlBlock" 
  Sequence="105" 
  Title="Application Page 4" > 

  <UrlAction Url="~site/_layouts/CustomApplicationPages/ApplicationPage4.aspx   
                  ?ItemId={ItemId}&amp;ListId={ListId}" 
    /> 
</CustomAction>

This CustomAction element is different than what you have seen before because it has a RegistrationType attribute that is assigned a value of List. It also is configured with a RegstrationID attribute that is assigned a value of 101. Notice that 101 is a list type identifier that applies to all document libraries. You should also notice that the Location attribute has a value of EditControlBlock, which creates the effect of adding the menu item to the ECB menu of documents within a document library, as shown in Figure 2-11.

Figure 2-11. A CustomAction element can add menu items to the ECB menu for items in a list or documents in a document library

If you look at the CustomAction element in this example, you see that the ActionUrl element is configured with a URL attribute that points to ApplicationPage4.aspx and appends the following QueryString.

../ApplicationPage4.aspx?ItemId={ItemId}&amp;ListId={ListId}

Notice the existence of the {ListId} token and the {ItemId} token within the QueryString that is appended to the end of the URL used to navigate to ApplicationPage4.aspx. Windows SharePoint Services dynamically replaces the {ListId} token with the identifying GUID for the current list or document library. Windows SharePoint Services dynamically replaces the {ItemId} token with the integer identifier for the current list item or document.

The code inside ApplicationPage4.cs is written under the assumption that there is information in the query string that can be used to identify a specific document and its hosting document library. Examine the following code to see how it pulls the required information out of the query string and uses it to create an SPList object and an SPListItem object.

// Get current site and web 
SPSite siteCollection = this.Site; 
SPWeb site = this.Web; 

// Access current list or document library 
string ListId = Request.QueryString["ListId"]; 
SPList list = site.Lists[new Guid(ListId)]; 

// Access current list item or document 
string ItemId = Request.QueryString["ItemId"]; 
SPListItem item = list.Items.GetItemById(Convert.ToInt32(ItemId));

In cases such as this one, you can assume that you are dealing with a document library and not simply a standard list type. In these situations, you can add code that converts the SPList object into a SPDocumentLibrary object. You can also access the document in question directly by creating an SPFile object as shown in the following sample.

// Get current site and web 
SPSite siteCollection = this.Site; 
SPWeb site = this.Web; 

// Access current list or document library 
string ListId = Request.QueryString["ListId"]; 
SPList list = site.Lists[new Guid(ListId)]; 
lblListTile.Text = list.Title; 
lblRootFolderUrl.Text = list.RootFolder.Url; 

// If current list is a document library... 
SPDocumentLibrary documentLibrary = (SPDocumentLibrary)list; 
lblDocumentTemplateUrl.Text = documentLibrary.DocumentTemplateUrl; 

// Access current list item or document 
string ItemId = Request.QueryString["ItemId"]; 
lblDocumentID.Text = ItemId; 
SPListItem item = list.Items.GetItemById(Convert.ToInt32(ItemId)); 
lblDocumentName.Text = item.Name; 
lblDocumentUrl.Text = item.Url; 

// If current list is a document library... 
SPFile file = site.GetFile(item.Url); 
lblFileAuthor.Text = file.Author.Name; 
lblFileSize.Text = file.TotalLength.ToString("0,###") + " bits"; 
lblFileLastModified.Text = "By " + file.ModifiedBy.Name + 
                           " on " + file.TimeLastModified.ToLocalTime().ToString(); 
lblFileCheckOutStatus.Text = file.CheckOutStatus.ToString();

The code you see in ApplicationPage4.cs uses the Windows SharePoint Services object model to discover information about a particular document and its document library. When you navigate to this page from the ECB menu item of a document within a document library, it displays the types of information shown in Figure 2-12.

Figure 2-12. An application page can be designed to display information about a specific list item or document

From the few examples we have presented, you should be able to see that creating custom application pages can be very valuable in a custom solution. You should also understand that what we have demonstrated here only scratches the surface of what is possible. You can create custom application pages to provide custom administration at the level of a site collection, site, list, item, document library, or document. You can also create a custom application page to provide a custom user interface for a list with user interface elements geared toward item entry, display, updating, or deletion. In summary, custom application pages can often provide the foundation of a custom Windows SharePoint Services solution.

Summary

This chapter began by reviewing important concepts and terminology used in IIS and ASP.NET. As you begin to learn how Windows SharePoint Services works internally, it is critical that you have a firm grasp of these topics. You have seen that Windows SharePoint Services converts an IIS Web site into a Web application by replacing several components within the HTTP Request Pipeline. You have also seen that the SPVirtualPathProvider component plays an essential role in the overall Windows SharePoint Services architecture because it provides the foundation for page customization.

This chapter also discussed the difference between site pages and application pages. While application pages do not support customization, they have a few key advantages over site pages. Namely, they perform better and can contain in-line code or code-behind. In this chapter, you have observed an approach for creating custom application pages and integrating them into the menus of a site by using CustomAction elements.

Now that you have a basic understanding of what can be done with custom application pages, it's time to turn our attention back to site pages. The next chapter examines the processing model for site pages in greater depth and discusses the creation of custom page templates, as well as how to provision them into page instances within a site.