Share via


The Page Class

In the .NET Framework, the Page class provides the basic behavior for all objects that an ASP.NET application builds by starting from .aspx files. Defined in the System.Web.UI namespace, the class derives from TemplateControl and implements the IHttpHandler interface:

public class Page : TemplateControl, IHttpHandler

In particular, TemplateControl is the abstract class that provides both ASP.NET pages and user controls with a base set of functionality. At the upper level of the hierarchy, we find the Control class. It defines the properties, methods, and events shared by all ASP.NET server-side elements—pages, controls, and user controls.

Derived from a class—TemplateControl—that implements INamingContainer, Page also serves as the naming container for all its constituent controls. In the .NET Framework, the naming container for a control is the first parent control that implements the INamingContainer interface. For any class that implements the naming container interface, ASP.NET creates a new virtual namespace in which all child controls are guaranteed to have unique names in the overall tree of controls. (This is also a very important feature for iterative data-bound controls, such as DataGrid, for user controls, and controls that fire server-side events.)

The Page class also implements the methods of the IHttpHandler interface, thus qualifying as the handler of a particular type of HTTP requests—those for .aspx files. The key element of the IHttpHandler interface is the ProcessRequest method, which is the method the ASP.NET runtime calls to start the page processing that will actually serve the request.

NoteINamingContainer is a marker interface that has no methods. Its presence alone, though, forces the ASP.NET runtime to create an additional namespace for naming the child controls of the page (or the control) that implements it. The Page class is the naming container of all the page s controls, with the clear exception of those controls that implement the INamingContainer interface themselves or are children of controls that implement the interface.

Properties of the Page Class

The properties of the Page object can be classified in three distinct groups: intrinsic objects, worker properties, and page-specific properties. The tables in the following sections enumerate and describe them.

Intrinsic Objects

Table 3-9 lists all properties that return a helper object that is intrinsic to the page. In other words, objects listed here are all essential parts of the infrastructure that allows for the page execution.

Table 3-9 ASP.NET Intrinsic Objects in the Page Class

Dd163816.table_C03625273_9(en-us,MSDN.10).png

We'll cover Request, Response, and Server in Chapter 14; Application and Session in Chapter 15; Cache will be the subject of Chapter 16. Finally, User and security will be the subject of Chapter 17.

Worker Properties

Table 3-10 details page properties that are both informative and provide the grounds for functional capabilities. You can hardly write code in the page without most of these properties.

Table 3-10 Worker Properties of the Page Class

Dd163816.table_C03625273_10(en-us,MSDN.10).png

In the context of an ASP.NET application, the Page object is the root of the hierarchy. For this reason, inherited properties such as NamingContainer and Parent always return null. The Page property, on the other hand, returns an instance of the same object (this in C# and Me in Visual Basic .NET).

The ViewStateUserKey property that has been added with version 1.1 of the .NET Framework deserves a special mention. A common use for the user key is to stuff user-specific information that would then be used to hash the contents of the view state along with other information. (See Chapter 15.) A typical value for the ViewStateUserKey property is the name of the authenticated user or the user s session ID. This contrivance reinforces the security level for the view state information and further lowers the likelihood of attacks. If you employ a user-specific key, an attacker ca't construct a valid view state for your user account unless the attacker can also authenticate as you. With this configuration, you have another barrier against one-click attacks. This technique, though, might not be effective for Web sites that allow anonymous access, unless you have some other unique tracking device running.

Note that if you plan to set the ViewStateUserKey property, you must do that during the Page_Init event. If you attempt to do it later (for example, when Page_Load fires), an exception will be thrown.

Context Properties

Table 3-11 lists properties that represent visual and nonvisual attributes of the page, such as the URL s query string, the client target, the title, and the applied style sheet.

Table 3-1 Page-Specific Properties of the Page Class

Dd163816.table_C03625273_11(en-us,MSDN.10).png

The three ID properties (ID, ClientID, and UniqueID) always return the empty string from a Page object. They make sense only for server controls.

Methods of the Page Class

The whole range of Page methods can be classified in a few categories based on the tasks each method accomplishes. A few methods are involved with the generation of the markup for the page; others are helper methods to build the page and manage the constituent controls. Finally, a third group collects all the methods that have to do with client-side scripting.

Rendering Methods

Table 3-12 details the methods that are directly or indirectly involved with the generation of the markup code.

Table 3-12 Methods for Markup Generation

Dd163816.table_C03625273_12(en-us,MSDN.10).png

In an ASP.NET page, no control can be placed outside a <form> tag with the runat attribute set to server. The VerifyRenderingInServerForm method is used by Web and HTML controls to ensure that they are rendered correctly. In theory, custom controls should call this method during the rendering phase. In many situations, the custom control embeds or derives an existing Web or HTML control that will make the check itself.

Not directly exposed by the Page class, but strictly related to it, is the GetWebResourceUrl method on the ClientScriptManager class in ASP.NET 2.0 and higher. The method provides a long-awaited feature to control developers. When you develop a control, you often need to embed static resources such as images or client script files. You can make these files be separate downloads but, even though it s effective, the solution looks poor and inelegant. Visual Studio .NET 2003 and newer versions allow you to embed resources in the control assembly, but how would you retrieve these resources programmatically and bind them to the control? For example, to bind an assembly-stored image to an <IMG> tag, you need a URL for the image. The GetWebResourceUrl method returns a URL for the specified resource. The URL refers to a new Web Resource service (webresource.axd) that retrieves and returns the requested resource from an assembly.

// Bind the <IMG> tag to the given GIF image in the control s assembly
img.ImageUrl = Page.GetWebResourceUrl(typeof(TheControl), GifName));

GetWebResourceUrl requires a Type object, which will be used to locate the assembly that contains the resource. The assembly is identified with the assembly that contains the definition of the specified type in the current AppDomain. If you re writing a custom control, the type will likely be the control s type. As its second argument, the GetWebResourceUrl method requires the name of the embedded resource. The returned URL takes the following form:

WebResource.axd?a=assembly&r=resourceName&t=timestamp

The timestamp value is the current timestamp of the assembly, and it is added to make the browser download resources again should the assembly be modified.

Table 3-13 details a bunch of helper methods on the Page class that are architected to let you manage and validate child controls and resolve URLs.

Table 3-13 Helper Methods of the Page Object

Dd163816.table_C03625273_13(en-us,MSDN.10).png

The methods LoadControl and LoadTemplate share a common code infrastructure but return different objects, as the following pseudocode shows:

public Control LoadControl(string virtualPath) {
    Control ascx = GetCompiledUserControlType(virtualPath);
    ascx.InitializeAsUserControl();
    return ascx;
}
public ITemplate LoadTemplate(string virtualPath) {
    Control ascx = GetCompiledUserControlType(virtualPath);
    return new SimpleTemplate(ascx);
}

Both methods differ from ParseControl in that the latter never causes compilation but simply parses the string and infers control information. The information is then used to create and initialize a new instance of the control class. As mentioned, the runat attribute is unnecessary in this context. In ASP.NET, the runat attribute is key, but in practice, it has no other role than marking the surrounding markup text for parsing and instantiation. It does not contain information useful to instantiate a control, and for this reason it can be omitted from the strings you pass directly to ParseControl.

Table 3-14 enumerates all the methods in the Page class that have to do with HTML and script code to be inserted in the client page.

Table 3-14 Script-Related Methods

Dd163816.table_C03625273_14(en-us,MSDN.10).png

As you can see, some methods in Table 3-14, which are defined and usable in ASP.NET 1.x, are marked obsolete. In ASP.NET 3.5 applications, you should avoid calling them and resort to methods with the same name exposed out of the ClientScript property. (See Table 3-10.)

// Avoid this in ASP.NET 3.5
Page.RegisterArrayDeclaration(…);
// Use this in ASP.NET 3.5
Page.ClientScript.RegisterArrayDeclaration(…);

We'll return to ClientScript in Chapter 5.

Methods listed in Table 3-14 let you emit JavaScript code in the client page. When you use any of these methods, you actually tell the page to insert that script code when the page is rendered. So when any of these methods execute, the script-related information is simply cached in internal structures and used later when the page object generates its HTML text.

Events of the Page Class

The Page class fires a few events that are notified during the page life cycle. As Table 3-15 shows, some events are orthogonal to the typical life cycle of a page (initialization, postback, rendering phases) and are fired as extra-page situations evolve. Let s briefly review the events and then attack the topic with an in-depth discussion on the page life cycle.

Table 3-15 Events That a Page Can Fire

Dd163816.table_C03625273_15(en-us,MSDN.10).png

The Eventing Model

When a page is requested, its class and the server controls it contains are responsible for executing the request and rendering HTML back to the client. The communication between the client and the server is stateless and disconnected because of the HTTP protocol. Real-world applications, though, need some state to be maintained between successive calls made to the same page. With ASP, and with other server-side development platforms such as Java Server Pages and Linux-based systems (for example, LAMP), the programmer is entirely responsible for persisting the state. In contrast, ASP.NET provides a built-in infrastructure that saves and restores the state of a page in a transparent manner. In this way, and in spite of the underlying stateless protocol, the client experience appears to be that of a continuously executing process. It s just an illusion, though.

Introducing the View State

The illusion of continuity is created by the view state feature of ASP.NET pages and is based on some assumptions about how the page is designed and works. Also, server-side Web controls play a remarkable role. Briefly, before rendering its contents to HTML, the page encodes and stuffs into a persistence medium (typically, a hidden field) all the state information that the page itself and its constituent controls want to save. When the page posts back, the state information is deserialized from the hidden field and used to initialize instances of the server controls declared in the page layout.

The view state is specific to each instance of the page because it is embedded in the HTML. The net effect of this is that controls are initialized with the same values they had the last time the view state was created—that is, the last time the page was rendered to the client. Furthermore, an additional step in the page life cycle merges the persisted state with any updates introduced by client-side actions. When the page executes after a postback, it finds a stateful and up-to-date context just as it is working over a continuous point-to-point connection.

Two basic assumptions are made. The first assumption is that the page always posts to itself and carries its state back and forth. The second assumption is that the server-side controls have to be declared with the runat=server attribute to spring to life once the page posts back.

The Single Form Model

Admittedly, for programmers whose experience is with ASP or JSP, the single form model of ASP.NET can be difficult to make sense of at first. These programmers frequently ask questions on forums and newsgroups such as, "Where s the Action property of the form?" and "Why ca't I redirect to a particular page when a form is submitted?"

ASP.NET pages are built to support exactly one server-side <form> tag. The form must include all the controls you want to interact with on the server. Both the form and the controls must be marked with the runat attribute; otherwise, they will be considered as plain text to be output verbatim. A server-side form is an instance of the HtmlForm class. The HtmlForm class does not expose any property equivalent to the Action property of the HTML <form> tag. The reason is that an ASP.NET page always posts to itself. Unlike the Action property, other common form properties such as Method and Target are fully supported.

Valid ASP.NET pages are also those that have no server-side forms and those that run HTML forms—a <form> tag without the runat attribute. In an ASP.NET page, you can also have both HTML and server forms. In no case, though, can you have more than one <form> tag with the runat attribute set to server. HTML forms work as usual and let you post to any page in the application. The drawback is that in this case no state will be automatically restored. In other words, the ASP.NET Web Forms model works only if you use exactly one server <form> element. We'll return to this topic in Chapter 5.

Asynchronous Pages

ASP.NET pages are served by an HTTP handler like an instance of the Page class. Each request takes up a thread in the ASP.NET thread pool and releases it only when the request completes. What if a frequently requested page starts an external and particularly lengthy task? The risk is that the ASP.NET process is idle but has no free threads in the pool to serve incoming requests for other pages. This is mostly due to the fact that HTTP handlers, including page classes, work synchronously. To alleviate this issue, ASP.NET supports asynchronous handlers since version 1.0 through the IHTTPAsyncHandler interface. Starting with ASP.NET 2.0, creating asynchronous pages is even easier thanks to specific support from the framework.

Two aspects characterize an asynchronous ASP.NET page: a new attribute on the @Page directive, and one or more tasks registered for asynchronous execution. The asynchronous task can be registered in either of two ways. You can define a Begin/End pair of asynchronous handlers for the PreRenderComplete event or create a PageAsyncTask object to represent an asynchronous task. This is generally done in the Page_Load event, but any time is fine provided that it happens before the PreRender event fires.

In both cases, the asynchronous task is started automatically when the page has progressed to a well-known point. Let s dig out more details.

Note An ASP.NET asynchronous page is still a class that derives from Page. There are no special base classes to inherit for building asynchronous pages.

The Async Attribute

The new Async attribute on the @Page directive accepts a Boolean value to enable or disable asynchronous processing. The default value is false.

<%@ Page Async="true" ... %>

The Async attribute is merely a message for the page parser. When used, the page parser implements the IHttpAsyncHandler interface in the dynamically generated class for the .aspx resource. The Async attribute enables the page to register asynchronous handlers for the PreRenderComplete event. No additional code is executed at run time as a result of the attribute.

Let s consider a request for a TestAsync.aspx page marked with the Async directive attribute. The dynamically created class, named ASP.TestAsync_aspx, is declared as follows:

public class TestAsync_aspx : TestAsync, IHttpHandler, IHttpAsyncHandler 
{
   ...
}

TestAsync is the code file class and inherits from Page, or a class that in turn inherits from Page. IHttpAsyncHandler is the canonical interface used for serving resources asynchronously since ASP.NET 1.0.

The AddOnPreRenderCompleteAsync Method

The AddOnPreRenderCompleteAsync method adds an asynchronous event handler for the page s PreRenderComplete event. An asynchronous event handler consists of a Begin/End pair of event handler methods, as shown here:

AddOnPreRenderCompleteAsync (
    new BeginEventHandler(BeginTask),
    new EndEventHandler(EndTask)
);

The BeginEventHandler and EndEventHandler are delegates defined as follows:

IAsyncResult BeginEventHandler(
    object sender,
    EventArgs e, 
    AsyncCallback cb, 
    object state)
void EndEventHandler(
    IAsyncResult ar)

In the code file, you place a call to AddOnPreRenderCompleteAsync as soon as you can, and always earlier than the PreRender event can occur. A good place is usually the Page_Load event. Next, you define the two asynchronous event handlers.

The Begin handler is responsible for starting any operation you fear can block the underlying thread for too long. The handler is expected to return an IAsyncResult object to describe the state of the asynchronous task. The End handler completes the operation and updates the page s user interface and controls. Note that you don t necessarily have to create your own object that implements the IAsyncResult interface. In most cases, in fact, to start lengthy operations you just use built-in classes that already implement the asynchronous pattern and provide IAsyncResult ready-made objects.

Important The Begin and End event handlers are called at different times and generally on different pooled threads. In between the two methods calls, the lengthy operation takes place. From the ASP.NET runtime perspective, the Begin and End events are similar to serving distinct requests for the same page. It s as if an asynchronous request is split in two distinct steps—a Begin and End step. Each request is always served by a pooled thread. Typically, the Begin step is served by a thread picked up from the ASP.NET worker thread pool. The End step is served by a thread selected from the completion thread pool.

The page progresses up to entering the PreRenderComplete stage. You have a pair of asynchronous event handlers defined here. The page executes the Begin event, starts the lengthy operation, and is then suspended until the operation terminates. When the work has been completed, the HTTP runtime processes the request again. This time, though, the request processing begins at a later stage than usual. In particular, it begins exactly where it left off—that is, from the PreRenderComplete stage. The End event executes, and the page finally completes the rest of its life cycle, including view-state storage, markup generation, and unloading.

The Significance of PreRenderComplete

So an asynchronous page executes up until the PreRenderComplete stage is reached and then blocks while waiting for the asynchronous operation to complete. When the operation is finally accomplished, the page execution resumes from the PreRenderComplete stage. A good question to ask would be the following: "Why PreRenderComplete?" What makes PreRenderComplete such a special event?

By design, in ASP.NET there s a single unwind point for asynchronous operations (also familiarly known as the async point). This point is located between the PreRender and PreRenderComplete events. When the page receives the PreRender event, the async point hasn t been reached yet. When the page receives PreRenderComplete, the async point has passed.

Building a Sample Asynchronous Page

Let s roll a first asynchronous test page to download and process some RSS feeds. The page markup is quite simple indeed:

<%@ Page Async="true" Language="C#" AutoEventWireup="true" 
         CodeFile="TestAsync.aspx.cs" Inherits="TestAsync" %>
<html>
<body>
    <form id="form1" runat="server">
        <% = rssData %>
    </form>
</body>
</html>

The code file is shown next, and it attempts to download the RSS feed from my personal blog:

public partial class TestAsync : System.Web.UI.Page
{
    const string RSSFEED = "https://weblogs.asp.net/despos/rss.aspx";
    private WebRequest req;
    public string rssData;

    void Page_Load (object sender, EventArgs e)
    {
        AddOnPreRenderCompleteAsync (
            new BeginEventHandler(BeginTask),
            new EndEventHandler(EndTask));
    }

    IAsyncResult BeginTask(object sender, 
                           EventArgs e, AsyncCallback cb, object state)
    {
        // Trace 
        Trace.Warn("Begin async: Thread=" + 
                    Thread.CurrentThread.ManagedThreadId.ToString());

        // Prepare to make a Web request for the RSS feed
        req = WebRequest.Create(RSSFEED);

        // Begin the operation and return an IAsyncResult object  
        return req.BeginGetResponse(cb, state);
    }

    void EndTask(IAsyncResult ar)
    {
        // This code will be called on a pooled thread 

        string text;
        using (WebResponse response = req.EndGetResponse(ar))
        {
            StreamReader reader;
            using (reader = new StreamReader(response.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }

            // Process the RSS data
            rssData = ProcessFeed(text);
        }

        // Trace
        Trace.Warn("End async: Thread=" + 
                    Thread.CurrentThread.ManagedThreadId.ToString());
        
        // The page is updated using an ASP-style code block in the ASPX 
        // source that displays the contents of the rssData variable
    }

    string ProcessFeed(string feed)
    {
        // Build the page output from the XML input  
        ...
    }
}

As you can see, such an asynchronous page differs from a standard one only for the aforementioned elements—the Async directive attribute and the pair of asynchronous event handlers. Figure 3-6 shows the sample page in action.

Dd163816.figure_C03625273_6(en-us,MSDN.10).png

Figure 3-6 A sample asynchronous page downloading links from an RSS feed.

It would also be interesting to take a look at the messages traced by the page. Figure 3-7 provides visual clues of it. The Begin and End stages are served by different threads and take place at different times.

Dd163816.figure_C03625273_7(en-us,MSDN.10).png

Figure 3-7 The traced request details clearly show the two steps needed to process a request asynchronously.

Note the time elapsed between the time we enter BeginTask and exit EndTask stages (indicated by the elapsed time between the "Begin async" and "End async" entries shown in Figure 3-7). It is much longer than intervals between any other two consecutive operations. It s in that interval that the lengthy operation—in this case, downloading and processing the RSS feed—took place. The interval also includes the time spent to pick up another thread from the pool to serve the second part of the original request.

The RegisterAsyncTask Method

The AddOnPreRenderCompleteAsync method is not the only tool you have to register an asynchronous task. The RegisterAsyncTask method is, in most cases, an even better solution. RegisterAsyncTask is a void method and accepts a PageAsyncTask object. As the name suggests, the PageAsyncTask class represents a task to execute asynchronously.

The following code shows how to rework the sample page that reads some RSS feed and make it use the RegisterAsyncTask method:

void Page_Load (object sender, EventArgs e)
{
    PageAsyncTask task = new PageAsyncTask(
        new BeginEventHandler(BeginTask),
        new EndEventHandler(EndTask),
        null,
        null);

    RegisterAsyncTask(task);
}

The constructor accepts up to five parameters, as shown in the following code:

public PageAsyncTask(
     BeginEventHandler beginHandler, 
     EndEventHandler endHandler, 
     EndEventHandler timeoutHandler, 
     object state, 
     bool executeInParallel)

The beginHandler and endHandler parameters have the same prototype as the corresponding handlers we use for the AddOnPreRenderCompleteAsync method. Compared to the AddOnPreRenderCompleteAsync method, PageAsyncTask lets you specify a timeout function and an optional flag to enable multiple registered tasks to execute in parallel.

The timeout delegate indicates the method that will get called if the task is not completed within the asynchronous timeout interval. By default, an asynchronous task times out if not completed within 45 seconds. You can indicate a different timeout in either the configuration file or the @Page directive. Here s what you need if you opt for the web.config file:

<system.web>
    <pages asyncTimeout="30" />
</system.web>

The @Page directive contains an integer AsyncTimeout attribute that you set to the desired number of seconds. Note that configuring the asynchronous timeout in web.config causes all asynchronous pages to use the same timeout value. Individual pages are still free to set their own timeout value in their @Page directive.

Just as with the AddOnPreRenderCompleteAsync method, you can pass some state to the delegates performing the task. The state parameter can be any object.

The execution of all tasks registered is automatically started by the Page class code just before the async point is reached. However, by placing a call to the ExecuteRegisteredAsyncTasks method on the Page class, you can take control of this aspect.

Choosing the Right Approach

When should you use AddOnPreRenderCompleteAsync, and when is RegisterAsyncTask a better option? Functionally speaking, the two approaches are nearly identical. In both cases, the execution of the request is split in two parts—before and after the async point. So where s the difference?

The first difference is logical. RegisterAsyncTask is an API designed to run tasks asynchronously from within a page—and not just asynchronous pages with Async=true. AddOnPreRenderCompleteAsync is an API specifically designed for asynchronous pages. This said, a couple of further differences exist.

One is that RegisterAsyncTask executes the End handler on a thread with a richer context than AddOnPreRenderCompleteAsync. The thread context includes impersonation and HTTP context information that is missing in the thread serving the End handler of a classic asynchronous page. In addition, RegisterAsyncTask allows you to set a timeout to ensure that any task doesn t run for more than a given number of seconds.

The other difference is that RegisterAsyncTask makes significantly easier the implementation of multiple calls to remote sources. You can have parallel execution by simply setting a Boolean flag, and you don t need to create and manage your own IAsyncResult object.

The bottom line is that you can use either approach for a single task, but you should opt for RegisterAsyncTask when you have multiple tasks to execute simultaneously.

Note For more information on asynchronous pages, check out Chapter 5 of my book Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics (Microsoft Press 2006).

Async-Compliant Operations

Which required operations force, or at least strongly suggest, the adoption of an asynchronous page? Any operation can be roughly labeled in either of two ways: CPU bound or I/O bound. CPU bound indicates an operation whose completion time is mostly determined by the speed of the processor and amount of available memory. I/O bound indicates the opposite situation, where the CPU mostly waits for other devices to terminate.

The need for asynchronous processing arises when an excessive amount of time is spent getting data in to and out of the computer in relation to the time spent processing it. In such situations, the CPU is idle or underused and spends most of its time waiting for something to happen. In particular, I/O-bound operations in the context of ASP.NET applications are even more harmful because serving threads are blocked too, and the pool of serving threads is a finite and critical resource. You get real performance advantages if you use the asynchronous model on I/O-bound operations.

Typical examples of I/O-bound operations are all operations that require access to some sort of remote resource or interaction with external hardware devices. Operations on non-local databases and non-local Web service calls are the most common I/O-bound operations for which you should seriously consider building asynchronous pages.

< Back      Next >