Anatomy of an ASP.NET Page

  • 2/15/2011

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, you 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, the Page class 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 a very important feature for iterative data-bound controls, such as DataGrid, and for user controls.)

The Page class also implements the methods of the IHttpHandler interface, thus qualifying it 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.

Properties of the Page Class

The properties of the Page class 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 5-8 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 5-8 ASP.NET Intrinsic Objects in the Page Class

Property

Description

Application

Instance of the HttpApplicationState class; represents the state of the application. It is functionally equivalent to the ASP intrinsic Application object.

Cache

Instance of the Cache class; implements the cache for an ASP.NET application. More efficient and powerful than Application, it supports item priority and expiration.

Profile

Instance of the ProfileCommon class; represents the user-specific set of data associated with the request.

Request

Instance of the HttpRequest class; represents the current HTTP request.

Response

Instance of the HttpResponse class; sends HTTP response data to the client.

RouteData

Instance of the RouteData class; groups information about the selected route (if any) and its values and tokens. (Routing in Web Forms is covered in Chapter 4, “xxx.”) The object is supported only in ASP.NET 4.

Server

Instance of the HttpServerUtility class; provides helper methods for processing Web requests.

Session

Instance of the HttpSessionState class; manages user-specific data.

Trace

Instance of the TraceContext class; performs tracing on the page.

User

An IPrincipal object that represents the user making the request.

I’ll cover Request, Response, and Server in Chapter 16; Application and Session are covered in Chapter 17; Cache will be the subject of Chapter 19. Finally, User and security will be the subject of Chapter 19, “ASP.NET Security.”

Worker Properties

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

TABLE 5-9 Worker Properties of the Page Class

Property

Description

AutoPostBackControl

Gets a reference to the control within the page that caused the postback event.

ClientScript

Gets a ClientScriptManager object that contains the client script used on the page.

Controls

Returns the collection of all the child controls contained in the current page.

ErrorPage

Gets or sets the error page to which the requesting browser is redirected in case of an unhandled page exception.

Form

Returns the current HtmlForm object for the page.

Header

Returns a reference to the object that represents the page’s header. The object implements IPageHeader.

IsAsync

Indicates whether the page is being invoked through an asynchronous handler.

IsCallback

Indicates whether the page is being loaded in response to a client script callback.

IsCrossPagePostBack

Indicates whether the page is being loaded in response to a postback made from within another page.

IsPostBack

Indicates whether the page is being loaded in response to a client postback or whether it is being loaded for the first time.

IsValid

Indicates whether page validation succeeded.

Master

Instance of the MasterPage class; represents the master page that determines the appearance of the current page.

MasterPageFile

Gets and sets the master file for the current page.

NamingContainer

Returns null.

Page

Returns the current Page object.

PageAdapter

Returns the adapter object for the current Page object.

Parent

Returns null.

PreviousPage

Returns the reference to the caller page in case of a cross-page postback.

TemplateSourceDirectory

Gets the virtual directory of the page.

Validators

Returns the collection of all validation controls contained in the page.

ViewStateUserKey

String property that represents a user-specific identifier used to hash the view-state contents. This trick is a line of defense against one-click attacks.

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 deserves a special mention. A common use for the user key is to stuff user-specific information that is then used to hash the contents of the view state along with other information. 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 can’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 5-10 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 5-10 Page-Specific Properties of the Page Class

Property

Description

ClientID

Always returns the empty string.

ClientIDMode

Determines the algorithm to use to generate the ID of HTML elements being output as part of a control’s markup. This property requires ASP.NET 4.

ClientQueryString

Gets the query string portion of the requested URL.

ClientTarget

Set to the empty string by default; allows you to specify the type of browser the HTML should comply with. Setting this property disables automatic detection of browser capabilities.

EnableViewState

Indicates whether the page has to manage view-state data. You can also enable or disable the view-state feature through the EnableViewState attribute of the @Page directive.

EnableViewStateMac

Indicates whether ASP.NET should calculate a machine-specific authentication code and append it to the page view state.

EnableTheming

Indicates whether the page supports themes.

ID

Always returns the empty string.

MetaDescription

Gets and sets the content of the description meta tag. This property requires ASP.NET 4.

MetaKeywords

Gets and sets the content of the keywords meta tag. This property requires ASP.NET 4.

MaintainScrollPositionOnPostback

Indicates whether to return the user to the same position in the client browser after postback.

SmartNavigation

Indicates whether smart navigation is enabled. Smart navigation exploits a bunch of browser-specific capabilities to enhance the user’s experience with the page.

StyleSheetTheme

Gets or sets the name of the style sheet applied to this page.

Theme

Gets and sets the theme for the page. Note that themes can be programmatically set only in the PreInit event.

Title

Gets or sets the title for the page.

TraceEnabled

Toggles page tracing on and off.

TraceModeValue

Gets or sets the trace mode.

UniqueID

Always returns the empty string.

ViewStateEncryptionMode

Indicates if and how the view state should be encrypted.

ViewStateMode

Enables the view state for an individual control even if the view state is disabled for the page. This property requires ASP.NET 4.

Visible

Indicates whether ASP.NET has to render the page. If you set Visible to false, ASP.NET doesn’t generate any HTML code for the page. When Visible is false, only the text explicitly written using Response.Write hits the client.

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 related to client-side scripting.

Rendering Methods

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

TABLE 5-11 Methods for Markup Generation

Method

Description

DataBind

Binds all the data-bound controls contained in the page to their data sources. The DataBind method doesn’t generate code itself but prepares the ground for the forthcoming rendering.

RenderControl

Outputs the HTML text for the page, including tracing information if tracing is enabled.

VerifyRenderingInServerForm

Controls call this method when they render to ensure that they are included in the body of a server form. The method does not return a value, but it throws an exception in case of error.

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. (You get a reference to the current client script manager through the ClientScript property on Page.) When you develop a custom control, you often need to embed static resources such as images or client script files. You can make these files be separate downloads; however, even though it’s effective, the solution looks poor and inelegant. Visual Studio allows 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 if the assembly is modified.

Controls-Related Methods

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

TABLE 5-12 Helper Methods of the Page Object

Method

Description

DesignerInitialize

Initializes the instance of the Page class at design time, when the page is being hosted by RAD designers such as Visual Studio.

FindControl

Takes a control’s ID and searches for it in the page’s naming container. The search doesn’t dig out child controls that are naming containers themselves.

GetTypeHashCode

Retrieves the hash code generated by ASP.xxx_aspx page objects at run time. In the base Page class, the method implementation simply returns 0; significant numbers are returned by classes used for actual pages.

GetValidators

Returns a collection of control validators for a specified validation group.

HasControls

Determines whether the page contains any child controls.

LoadControl

Compiles and loads a user control from an .ascx file, and returns a Control object. If the user control supports caching, the object returned is PartialCachingControl.

LoadTemplate

Compiles and loads a user control from an .ascx file, and returns it wrapped in an instance of an internal class that implements the ITemplate interface. The internal class is named SimpleTemplate.

MapPath

Retrieves the physical, fully qualified path that an absolute or relative virtual path maps to.

ParseControl

Parses a well-formed input string, and returns an instance of the control that corresponds to the specified markup text. If the string contains more controls, only the first is taken into account. The runat attribute can be omitted. The method returns an object of type Control and must be cast to a more specific type.

RegisterRequiresControlState

Registers a control as one that requires control state.

RegisterRequiresPostBack

Registers the specified control to receive a postback handling notice, even if its ID doesn’t match any ID in the collection of posted data. The control must implement the IPostBackDataHandler interface.

RegisterRequiresRaiseEvent

Registers the specified control to handle an incoming postback event. The control must implement the IPostBackEventHandler interface.

RegisterViewStateHandler

Mostly for internal use, the method sets an internal flag that causes the page view state to be persisted. If this method is not called in the prerendering phase, no view state will ever be written. Typically, only the HtmlForm server control for the page calls this method. There’s no need to call it from within user applications.

ResolveUrl

Resolves a relative URL into an absolute URL based on the value of the TemplateSourceDirectory property.

Validate

Instructs any validation controls included in the page to validate their assigned information. If defined in the page, the method honors ASP.NET validation groups.

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 the ParseControl method 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.

Script-Related Methods

Table 5-13 enumerates all the methods in the Page class related to HTML and script code to be inserted in the client page.

TABLE 5-13 Script-Related Methods

Method

Description

GetCallbackEventReference

Obtains a reference to a client-side function that, when invoked, initiates a client callback to server-side events.

GetPostBackClientEvent

Calls into GetCallbackEventReference.

GetPostBackClientHyperlink

Appends javascript: to the beginning of the return string received from GetPostBackEventReference. For example:

javascript:__doPostBack(‘CtlID’,’’)

GetPostBackEventReference

Returns the prototype of the client-side script function that causes, when invoked, a postback. It takes a Control and an argument, and it returns a string like this:

__doPostBack(‘CtlID’,’’)

IsClientScriptBlockRegistered

Determines whether the specified client script is registered with the page. It’s marked as obsolete.

IsStartupScriptRegistered

Determines whether the specified client startup script is registered with the page. It’s marked as obsolete.

RegisterArrayDeclaration

Use this method to add an ECMAScript array to the client page. This method accepts the name of the array and a string that will be used verbatim as the body of the array. For example, if you call the method with arguments such as theArray and “’a’, ‘b’”, you get the following JavaScript code:

var theArray = new Array(‘a’, ‘b’);

It’s marked as obsolete.

RegisterClientScriptBlock

An ASP.NET page uses this method to emit client-side script blocks in the client page just after the opening tag of the HTML <form> element. It’s marked as obsolete.

RegisterHiddenField

Use this method to automatically register a hidden field on the page. It’s marked as obsolete.

RegisterOnSubmitStatement

Use this method to emit client script code that handles the client OnSubmit event. The script should be a JavaScript function call to client code registered elsewhere. It’s marked as obsolete.

RegisterStartupScript

An ASP.NET page uses this method to emit client-side script blocks in the client page just before closing the HTML <form> element. It’s marked as obsolete.

SetFocus

Sets the browser focus to the specified control.

As you can see, some methods in Table 5-13, which are defined and usable in ASP.NET 1.x, are marked as obsolete. In ASP.NET 4 applications, you should avoid calling them and resort to methods with the same name exposed out of the ClientScript property.

// Avoid this in ASP.NET 4
Page.RegisterArrayDeclaration(...);

// Use this in ASP.NET 4
Page.ClientScript.RegisterArrayDeclaration(...);

The ClientScript property returns an instance of the ClientScriptManager class and represents the central console for registering script code to be programmatically emitted within the page.

Methods listed in Table 5-13 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 5-14 shows, some events are orthogonal to the typical life cycle of a page (initialization, postback, and 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 of the page life cycle.

TABLE 5-14 Events a Page Can Fire

Event

Description

AbortTransaction

Occurs for ASP.NET pages marked to participate in an automatic transaction when a transaction aborts

CommitTransaction

Occurs for ASP.NET pages marked to participate in an automatic transaction when a transaction commits

DataBinding

Occurs when the DataBind method is called on the page to bind all the child controls to their respective data sources

Disposed

Occurs when the page is released from memory, which is the last stage of the page life cycle

Error

Occurs when an unhandled exception is thrown.

Init

Occurs when the page is initialized, which is the first step in the page life cycle

InitComplete

Occurs when all child controls and the page have been initialized

Load

Occurs when the page loads up, after being initialized

LoadComplete

Occurs when the loading of the page is completed and server events have been raised

PreInit

Occurs just before the initialization phase of the page begins

PreLoad

Occurs just before the loading phase of the page begins

PreRender

Occurs when the page is about to render

PreRenderComplete

Occurs just before the pre-rendering phase begins

SaveStateComplete

Occurs when the view state of the page has been saved to the persistence medium

Unload

Occurs when the page is unloaded from memory but not yet disposed of

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 it’s based on 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 PHP, 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. In brief, 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 when the page posts back.

The Single Form Model

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 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 9.

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 happens mostly because HTTP handlers, including page classes, work synchronously. To alleviate this issue, ASP.NET has supported asynchronous handlers since version 1.0 through the IHTTPAsyncHandler interface. Starting with ASP.NET 2.0, creating asynchronous pages was made easier thanks to specific support from the framework.

Two aspects characterize an asynchronous ASP.NET page: a tailor-made 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.

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 that has been 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 call can be simplified as follows:

AddOnPreRenderCompleteAsync(BeginTask, EndTask);

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. When the lengthy task has completed, the End handler finalizes the original request 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.

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 requested operation to complete asynchronously. 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 = "http://weblogs.asp.net/despos/rss.aspx";
    private WebRequest req;

    public String RssData { get; set; }

    void Page_Load (Object sender, EventArgs e)
    {
        AddOnPreRenderCompleteAsync(BeginTask, 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(nother) pooled thread

        using (var response = req.EndGetResponse(ar))
        {
            String text;
            using (var 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 5-4 shows the sample page in action.

Figure 5-4

FIGURE 5-4 A sample asynchronous page downloading links from a blog.

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

Note the time elapsed between the Exit BeginTask and Enter EndTask stages. 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.

Figure 5-5

FIGURE 5-5 The traced request details clearly show the two steps needed to process a request asynchronously.

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 you 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 it’s 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.

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. That 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 the implementation of multiple calls to remote sources significantly easier. 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.

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 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.