The ASP.NET Page Class


Much of the code you write interacts with the ASP.NET page through the Page object. The Page acts as a container for the controls you place on your Web Form. It also provides properties and methods that integrate with the controls and the underlying ASP.NET execution cycle. In fact, there are 15 events, around 50 methods, and over 60 properties for the Page objectthough in reality, only a few are regularly used.

A full list of the properties, methods, and events for the Page class is at http://msdn2.microsoft.com/library/68987swh(en-us,vs.80).aspx.


To help you get a feel for the features of the Page class, the following sections demonstrate some of the more useful techniques:

  • General Methods and Properties of the Page Class

  • Accessing the Intrinsic ASP.NET Objects

  • Finding Controls on a Page

  • Writing Trace Information

  • Skins and Themes

  • User Input Validation

  • The Page-level Events

The Page class also exposes the IsCrossPagePostBack and PreviousPage properties, which are useful when you take advantage of cross-page posting techniques (posting a form to a different ASP.NET page instead of using a postback). The next chapter discusses these, when you look in more detail at navigation techniques in ASP.NET. There are also two properties, Master and MasterPageFile, concerned with the use of Master Pages. You will see this topic discussed toward the end of this chapter.


General Methods and Properties of the Page Class

The Page object exposes information about the current page and the current request, and allows you to set various parameters of the response. The read-only properties available include:

  • IsPostBack, which indicates whether the page execution is in response to a client postback, or if this is the first access

  • ClientQueryString, which returns the query string portion of the URL requested by the client

  • Form, which gets a reference to the server-side <form> on this page

  • Header, which gets a reference to the document <head> section of this page

  • Title, which gets a reference to the <title> section of this page

The write-only properties that specify features of the response are:

  • CodePage, which sets the code page identifier for this page

  • ContentType, which sets the HTTP MIME type for this page

  • Culture, which sets the execution culture ID for this page

  • UICulture, which sets the user interface culture ID for this page

  • LCID, which sets the locale identifier for this page

  • Buffer, which specifies whether the page output is buffered by IIS before being sent to the client (true), or is sent to the client as it is created (false)

  • ResponseEncoding, which sets the encoding language for this page

This example shows how you can use several of these properties, along with some you will come across later in this section of the chapter. In the Page_Load event, the code (see Listing 9.6) first sets the ClientTarget property of the page to the value selected in the drop-down list box. This list has the AutoPostBack property set to true so that a postback occurs whenever you change the selected value. Then the code creates a StringBuilder, appends the text to display by querying the properties of the Page class, and dumps the content into a Label control on the page. Notice how you can use the IsLocal property to ensure that remote users cannot see the path to this page on your site.

Listing 9.6. Using the Properties of the Page Class

// set the ClientTarget type from drop-down listbox Page.ClientTarget = lstClientTarget.SelectedValue; // get property values for display StringBuilder sb = new StringBuilder(); sb.Append("Page.IsPostBack ='"+ Page.IsPostBack.ToString()); sb.Append("Page.ClientQueryString ='"+ Page.ClientQueryString); sb.Append("Page.ClientTarget ='"+ Page.ClientTarget); sb.Append("Page.Form.Method ='"+ Page.Form.Method); sb.Append("Page.Title ='"+ Page.Title); sb.Append("Page.CodePageID ='"+ Page.CodePage); sb.Append("Page.ContentType ='"+ Page.ContentType); sb.Append("Page.Culture ='"+ Page.Culture); sb.Append("Page.UICulture ='"+ Page.UICulture); sb.Append("Page.LCID ='"+ Page.LCID); sb.Append("Page.Buffer ='"+ Page.Buffer.ToString()); sb.Append("Page.ResponseEncoding ='"+ Page.ResponseEncoding); sb.Append("(Page.Header as HtmlHead).TagName ='"                           + (Page.Header as HtmlHead).TagName); if (Request.IsLocal) {   sb.Append("Page.MapPath(Request.Path) ='"            + Page.MapPath(Request.Path) ); } else {   sb.Append("Page.MapPath(Request.Path) is only displayed "           + "for the local machine"); } lblResult.Text = sb.ToString();

The example shows how you can get the title of a page (even though the <title> element is not declared as a server control), plus the <head> element and its contents. In the example, the Header is cast to an HtmlHead instance, and then the properties of this element can be displayedsuch as the element (tag) name. Figure 9.4 shows this example page in action. Remember that many of the properties (in fact, all those you see here except for IsPostBack, ClientQueryString, Form, and Title) are read/writeso you can set the values of these as well as just querying them as in this example.

Figure 9.4. Querying and setting some properties of the Page class


Specifying the Client Target

There is a read/write property on the Page class named ClientTarget, which allows you to override the automatic detection of browser capabilities. ASP.NET automatically tailors the output it generates to suit the browser type (where possible). For example, it generates CSS style information only for browsers that support this, and uses <font> elements for other browsers. You can specify the values "ie5" (Internet Explorer 5.5 and later), "ie4" or "uplevel" (Internet Explorer 4.0 and later), or "downlevel" (other browsers) to control the output rendering type. Alternatively, you can specify any of the alias values defined in the <clientTarget> sections of your Web.Config or Machine.Config file, or a specific user agent string.

Managing ViewState

ASP.NET pages use a hidden control within the server-side <form> section to store encrypted data between postbacks that is required for recreating the correct page and control tree with the previous values. This data is the viewstate, discussed towards the end of the previous chapter. You can enable and disable the storage of viewstate using the EnableViewState property for the page, and you can specify if authentication of the encrypted viewstate takes place on postback by using the EnableViewStateMac property.

You can also assign an identifier to the viewstate for each individual user of the site by setting the ViewStateUserKey property and then checking this on postback to identify individual users. Finally, you can specify if viewstate encryption takes place by setting the ViewStateEncryptionMode property. See the next section, Accessing the Intrinsic ASP.NET Objects, which describes how you can store and retrieve values from the viewstate.

Handling Errors

When an error occurs, ASP.NET raises an exception. If you do not handle the exception in your code, it causes the default error page to display. Instead of the default error page, you can set the ErrorPage property of the Page instance to a page name or URL so that browser redirection will occur if an unhandled page exception occurs.

Translating URLs

The Page class exposes the MapPath method, which you can use to resolve a virtual relative or absolute URL path to the equivalent physical file path on your server. The example for the section General Methods and Properties of the Page Class (earlier in this chapter) demonstrates this method.

Accessing the Intrinsic ASP.NET Objects

You saw earlier in this chapter how you access and use the intrinsic ASP.NET objects by accessing the Request, Response, and Server properties of the current page context in your code. You can also work with several other intrinsic objects:

  • Application, which provides a global name/value dictionary for storing values that are accessible from any page, and by any user, within the current ASP.NET application (the current application virtual root).

  • Session, which provides a name/value dictionary for storing values that are accessible only to the current client, and only until the current session times out after a default value of 20 minutes with no further page access. This timeout is set in the Machine.Config or Web.Config file. See Chapter 16 for more details about configuring session state storage.

  • ViewState, which provides access to the name/value dictionary stored in a hidden control within the page. This is useful for storing small items of data and is accessible only across postbacks from this page.

  • Cache, which provides a global name/value dictionary for storing values that are accessible from any page, and by any user, within the current ASP.NET application (the current application virtual root). Unlike Application, however, Cache provides methods that exert more control over when and how items are expired from the cache and how caching behaves when memory constraints affect the application. Chapter 6 looks in detail at caching techniques in ASP.NET.

  • User, which provides access to the current user instance. When the application denies anonymous access using any of the available types of authentication, the User property of the Page contains a reference to an object that implements the IPrinciple interfacethrough which you can get access to details about the current user. Chapter 11 contains more information on the security features in ASP.NET.

You access the Application, Session, and Viewstate objects in the same waywith a key name that identifies the value you want to read or store. For example, you can store a value (which can be a simple value-type converted to a String or an object type that can self-serialize, such as a DataSet or DataTable) like this:

Application["MyString"] = "Value for my string"; Session["MyData"] = myDataSet; ViewState["MyInteger"] = iMyValue.ToString();


To retrieve values, you access them using the key name and cast the result to the appropriate type:

sMyString = Application["MyString"]; myDataSet = Session["MyData"] as DataSet; iMyValue  = (Int32)ViewState["MyInteger"];


Finding Controls on a Page

One of the common tasks when writing code is getting references to controls on the page, or the child controls of another control. Every "container" control (a control such as a table or form that can contain child controls) has a Controls property that provides a reference to a collection of any child controls. This applies to the Page itself, and to all the container controls in the Page.Controls collection. Thus, the "control tree"for the page is in fact a hierarchical tree-like structure.

All container controls, such as Page, have methods that help you locate a specific control when you do not know the control ID value, or when it is located within a repeating container control. The HasControls methods returns true if there are any child controls, or false if the Controls collection is empty. It is useful if you are iterating through the controls on a page looking for some specific control type or property value. The example here demonstrates how you can achieve this.

Iterating through the control tree is of necessity a recursive process, so the example page contains a routine named ShowControlTree (see Listing 9.7) that takes a control as a parameter, plus a reference to a node in the TReeView control declared elsewhere on the page. It sets the text value of this node to a string containing the type name of the control passed in (after removing the System.Web.UI prefix), plus the value of the ID property of the control.

Then the code iterates through all the child controls in the Controls collection of the current control. For each one, it creates a new TReeNode node within the current node, specifies it is to be displayed as plain text (not as a hyperlink), and adds it to the treeView control. Finally, it calls itself recursively with references to the current child control and the new tree-Node. This causes the routine to repeat the process for that control and all its child controls.

Listing 9.7. Iterating through the Controls in a Page and Adding Them to a TreeView

void ShowControlTree(Control ctrl, TreeNode node) {   // set text of the current node to show type and ID of control   node.Text = ctrl.ToString().Replace("System.Web.UI." , String.Empty)             + "[id='"+ ctrl.ID + "']";   // iterate through the Controls collection of this control   foreach (Control child in ctrl.Controls)   {     TreeNode nextnode = new TreeNode();     nextnode.SelectAction = TreeNodeSelectAction.None;     node.ChildNodes.Add(nextnode);     // recursively call this routine for all child controls     ShowControlTree(child, nextnode);   } }

To start the process off, the Page_Load event handler (see Listing 9.8) just has to create a new root node in the treeView, specify the SelectAction, and add it to the treeView in the same way as for the child controls in the ShowControlTree routine you just looked at. Then it can call the ShowControlTree routine with the Page as the root control and the new TReeView node. Figure 9.5 shows the results from this section of code, and you can clearly see the hierarchy of controls on the page.

Figure 9.5. Listing the controls in the page with a TreeView control


Listing 9.8. The Page_Load Event Handler That Populates the TreeView and Page

void Page_Load() {   // add a root node to the TreeView for the Page class   TreeNode node = new TreeNode();   node.SelectAction = TreeNodeSelectAction.None;   trvResult.Nodes.Add(node);   // call a recursive routine to populate this node and   // then iterate the child nodes doing the same thing   ShowControlTree(Page, node);   // search for all controls of type Label on this page   // using a similar recursive routine   StringBuilder sb = new StringBuilder(                      "<b>List of all Label controls</b>:<br />");   FindLabelControls(Page.Controls, sb);   lblResult.Text = sb.ToString(); }

Searching for Specific Control Types

The second section of code in the Page_Load event handler in Listing 9.8 starts a process of searching through the page's control tree to find all Label controls. After creating a StringBuilder to store the results, it calls another recursive routine named FindLabelControls, passing in a reference to the Controls collection of the Page and the StringBuilder. The result is then placed in another Label control declared in the HTML section of the page.

Listing 9.9 shows the FindLabelControls routine, and you can see that it is also a recursive routine. It starts by iterating through the Controls collection passed to it. For each control in the collection, it checks if it is a Label controland, if so, displays the value of the ID property. Then the code casts the reference to a Control instance into a Label instance so that the type-specific properties are available. In this case, the value of the EnableViewState property is extracted and displayed.

Listing 9.9. Searching for Label Controls in the Page's Control Tree

void FindLabelControls(ControlCollection cc, StringBuilder sb) {   // iterate through the Controls collection   foreach (Control ctrl in cc)   {     // see if it is a Label control     if (ctrl is Label)     {       sb.Append("ID ='"+ ctrl.ID + "' &nbsp; ");       // must cast Control to a specific type to get non-base       // properties such as EnableViewState       sb.Append("EnableViewState = "         + (ctrl as Label).EnableViewState.ToString() + "<br />");     }     // see if it has any child controls     if (ctrl.HasControls())     {       // recursively call this routine on the Controls       // collection of this child control       FindLabelControls(ctrl.Controls, sb);     }   } }

Finally, after checking that the Controls collection of the current control contains at least one control using the HasControls method, the code calls itself recursively. It passes in the Controls collection of the current control and the StringBuilder, so that all the child controls of the current control are processed.

Figure 9.6 shows the section of the page that the code in Listing 9.9 generates. The first four Label controls are actually declared in the Master Page, but they are part of the current control tree when the page executes. The last Label control is the one that displays the resultsthe one containing the text you are looking at.

Figure 9.6. A list of all the Label controls within the page's control tree


The FindControl Method

When a control is located within a repeating container control, such as the GridView, the actual ID value allocated to the control at runtime depends on the container(s) that hold the control. The ID value (visible if you select View | Source in your browser when viewing such a page) is a concatenation of the ID values of all the controls that contain this control, with the ID you specify for the control at design time appended.

The FindControl methods takes a control ID value and searches for a server control with the specified ID value within the current container, even if the actual ID of the control contains other concatenated control IDs. For example, you can find a control with the ID value MyCtrl anywhere on the page using:

Control ctrl = Page.FindControl("MyCtrl");


However, you will usually need to cast the control reference returned to the specific control type if you want to access its properties. For example:

TextBox txt = Page.FindControl("MyCtrl") as TextBox;


Be aware that you will get a null reference returned if the FindControl method cannot locate the control. The equivalent code in Visual Basic.NET is this:

Dim txt As TextBox = CType(Page.FindControl("MyCtrl"), TextBox)


Writing Trace Information

One of the great features in ASP.NET is the ability to write out trace information from your code, allowing you to more easily see and debug problem areas without having to resort to writing the information into the page. The TRaceContext instance for the current page, accessed through the TRace property of the Page, provides the Write and Warn methods for writing trace information.

This information appears at the end of the page when you set the TRace="true" attribute in the @Page directive or in a Web.Config file (the Warn method generates information that is displayed in red text). You can specify a value that acts as a category type for the first parameter, and the value to write for the second one, as follows:

Trace.Write("Loading", "MyValue=" + MyObject.MyValue.ToString());


Or you can omit the category:

Trace.Write("Load Completed");


You can also specify an Exception instance (or one of its descendent class instances) as the third parameter, so that the error details display as well. You can also test if tracing is currently enabled using the traceEnabled property (perhaps to avoid complex calculation of values if tracing is turned off), and you can specify the mode in which trace information is written using the traceModeValue property (sorted by time or by category). You will see tracing used in the example in a later section, Page-Level Events.

Skins and Themes

ASP.NET allows you to change the appearance of your pages and site using Themes. A Theme is a set of style declarations (usually called skins) that apply to specific controls. Themes can set the CSS style properties, images, colors, and other features. Like all visual controls that support Themes, the Page class exposes four properties that you can use. These are:

  • The SkinID property, which gets or sets the skin to apply to the control.

  • The StyleSheetTheme property, which gets or sets the name of the style sheet applied to this page.

  • The Theme property, which gets or sets the name of the page theme.

Chapter 12 looks in detail at the use of Themes along with the Personalization capabilities of ASP.NET.

Validation

The Page object interacts with all of the validation controls on a page (the previous chapter discussed the ASP.NET validation controls). During a postback, the Page.IsValid property indicates if any of the validation controls encountered an invalid value. You can get a reference to a collection of all the validation controls on the page from the Page.Validators property, or just the validation controls within a specified validation group using the Page.GetValidators method. Finally, you can cause all the validation controls to validate their values using the Page.Validate method.

Page-Level Events

The Page class exposes events that are raised as the request passes through the ASP.NET request pipeline (a series of modules that perform various operations including initialization and execution of the page), and you can handle these events in your code if you need to interact with the generation of the page. The first series of events occur on initialization, before the control tree is constructed. The three events are PreInit, Init, and InitComplete. The main use of the Init event is to set the Theme or Master Page for the page and to load details from the Personalization system.

The next step is the loading of the page, complete with the populated control tree and the values assigned to the controls. The three events are PreLoad (occurs before postback data is loaded into the controls on the page), Load (where you can interact with the fully populated controls), and LoadComplete. The Load event is the one you will use most of the time.

The final stage of sending the page content to the client is the series of render events. These are PreRender and PreRenderComplete. In general, you will not have to use these two events in page-level code. They are more suited to use in custom controls that you create, where you use the PreRender, Render, and PreRenderComplete events to generate output from the control.

There are also events that occur during the execution of a page that are not part of the page's own event sequence. These include the following:

  • AbortTransaction, which occurs when a user aborts a transaction

  • CommitTransaction, which occurs when a transaction completes

  • DataBinding, which occurs when the server control binds to a data source

  • Error, which occurs when code or a control throws an unhandled exception. This event is useful for catching errors and displaying a custom message, although you can achieve the same effect by configuring the customErrors section of the Web.Config file for the site

This example demonstrates how you can handle some of the page-level events. It handles the PreInit, Init, InitComplete, PreLoad, Load, PreRender, and PreRenderComplete events described earlier. For each one, there is an event handler declared in the page for that event. For example, Listing 9.10 shows the event handler for the LoadComplete event, and you can see that it writes to both the current TRaceContext instance and to a Label control on the page.

Listing 9.10. The Event Handler for the LoadComplete Event

protected void Page_LoadComplete(object sender, EventArgs e) {   Trace.Write("My Event Trace", "Page_LoadComplete event occurred.");   lblResult.Text += "Page_LoadComplete event occurred.<br />"; }

However, not all events are automatically wired up in the Page class, and so the handler for the first event to occur (PreInit) also attaches the handlers for the other events that require thisas you can see in Listing 9.11.

Listing 9.11. Wiring Up Event Handlers in the PreInit Event Handler

protected void Page_PreInit(object sender, EventArgs e) {   // display details of event   Trace.Write("My Event Trace", "Page_PreInit event occurred.");   lblResult.Text += "Page_PreInit event occurred.<br />";   // must attach some of the other events manually   // these are not wired up automatically for Page   Page.InitComplete += new EventHandler(Page_InitComplete);   Page.PreLoad += new EventHandler(Page_PreLoad);   Page.LoadComplete += new EventHandler(Page_LoadComplete);   Page.PreRenderComplete +=new EventHandler(Page_PreRenderComplete); }

In Visual Basic.NET, the syntax for wiring up an event handler is: AddHandler Page.InitComplete, New EventHandler (Page_ InitComplete)


One final feature of the example is that it handles the AbortTransaction event. For this to be possible, the page must specify that a transaction is required (here it uses the transaction attribute of the Page directive), and it must import the System.EnterpriseServices namespace so that it can reference the ContextUtil class:

<%@ Page Language="C#" Trace="true" Transaction="RequiresNew" %> <%@ Import Namespace="System.EnterpriseServices" %>


Listing 9.12 shows the event handler for the Page_Load event. It displays the same messages as all the other event handlers, and then calls the SetAbort method of the ContextUtil class that represents the current transaction context. This raises the AbortTransaction event, which the second event handler in Listing 9.12 handles. It simply writes to the TRace-Context, because the Page is now in the Render stage and so the contents of the Label control cannot be changed.

Listing 9.12. The Load and AbortTransaction Event Handlers for the Page

protected void Page_Load(object sender, EventArgs e) {   Trace.Write("My Event Trace", "Page_Load event occurred.");   lblResult.Text += "Page_Load event occurred.<br />";   // force ASP.NET to raise the AbortTransaction event   // uses the COM+ utility classes, so the page requires   // declaration of the System.EnterpriseServices namespace   ContextUtil.SetAbort(); } ... ... protected void Page_AbortTransaction(object sender, EventArgs e) {   // display message using Warn instead of Write   Trace.Warn("My Event Trace", "Page_AbortTransaction occurred."); }

Figure 9.7 shows the results of this example. You can see the text inserted into the Label control at the top of the page, followed by the trace information that ASP.NET generates automatically when tracing is enabled for the page. In the Trace Information section, you can see the messages created by the event handlers. The entry for the AbortTransaction event is in red because the code uses the Warn method instead of the Write method for this one.

Figure 9.7. The trace output showing the messages created by the event handlers


Client-Side Scripting Features

ASP.NET attempts to provide a browser-based environment that iswith respect to usability and feedbackas close as possible to that of a traditional executable application. However, to achieve this requires client-side script to run in the browser. Almost all the modern browsers support client-side JavaScript, and ASP.NET is designed (where possible) to provide a fallback model that works for browsers that do not support client-side script.

You can set the following four properties to control how the rendered page behaves in the client's browser:

  • The FocusControl method, which is mainly called directly on an individual control, rather than on the Page itself and which sets the input focus or cursor to that control when the page is rendered in the client's browser

  • The SetFocus method on the current Page instance, which sets the input focus or cursor to a specific control when the page is rendered in the client's browser

  • The MaintainScrollPositionOnPostBack property of the Page class, which when set to true, causes ASP.NET to attempt to set the input focus to the control that the user interacted with last, so that the page is scrolled back to the same position if it is longer than the browser window can accommodate

  • The SmartNavigation property, which determines if ASP.NET should attempt to generate a page that uses background requests to repopulate sections that change as the user interacts with the page. You will not normally set this property in code, but you can set it instead in the @Page directive

Creating Client-Side Script Sections

Client-side script, or <script> elements that reference client-side script held in separate files, can be included verbatim in the page you send to the client. However, it is often useful to inject client-side script dynamically at runtime. One issue is that, if you do this from a custom user control or server control, you must be sure that only one instance of the script is injectedirrespective of the number of instances of that control on the page.

The ClientScript property of the Page instance returns an instance of the ClientScriptManager class for the current page. This class exposes a series of methods that you can use to access and modify individual script sections, including getting references to various objects in the scripts and the page, checking if scripts are registered, and adding new scripts and hidden controls. These are some of the more useful methods of the ClientScriptManager class:

  • The RegisterClientScriptBlock method, which causes ASP.NET to emit the client-side script block defined within a String and to register it with the page using the unique name you provide

  • The IsClientScriptBlockRegistered method, which indicates if a client-side script block, identified by a unique name that you specify, is currently registered in this page

  • The RegisterStartupScript method, which causes ASP.NET to emit the client-side start-up script defined within a String and to register it with the page using the unique name you provide. A start-up script is code that runs as the page loads, and is located at the end of the <form> section of the page

  • The IsStartupScriptRegistered method, which indicates if a client-side startup script, identified by a unique name that you specify, is currently registered in this page

  • The RegisterClientScriptInclude method, which causes ASP.NET to emit a <script> element that loads a separate client-side script file and to register it with the page using the unique name you provide

  • The IsClientScriptIncludeRegistered method, which indicates if a <script> element that loads a separate client-side script file, identified by a unique name that you specify, is currently registered in this page

  • The RegisterOnSubmitStatement method, which causes ASP.NET to emit a client-side script function that executes before a postback submits the page and to register it with the page using the unique name you provide

  • The IsOnSubmitStatementRegistered method, which indicates if a client-side script function that executes before a postback submits the page, identified by a unique name that you specify, is currently registered in this page

  • The RegisterHiddenField method, which creates an <input type="hidden"> control in the page that can be used by other controls or client-side code to submit values to the server during a postback

A full list of the methods for the ClientScriptManager class is at http://msdn2.microsoft.com/library/asxkek04(en-us,vs.80).aspx.


Asynchronous Page Callbacks

One of the features of client-side Web programming that was popular some years ago, but then seemed to die away in the quest for universal browser compatibility, has recently started to become more useful. This is because modern browsers support an increasingly wide and useful set of universal standards that allow the creation of client-side code that runs on all these browsers.

Currently, much of the focus is on an environment called Ajax (available from http://ajax.schwarz-interactive.de/csharpsample/default.aspx), which implements asynchronous client-side callbacks to the server from client-side script running in the browser, without requiring a page reload. A good example of this approach is the GoogleMaps site at http://maps.google.co.uk/.

Microsoft is also developing (at the time of writing) a client-side script library and integrated server-side system called Atlas, which will provide a range of features to enable asynchronous page operations (see http://atlas.asp.net/).

You can write your own custom code or use the Atlas or Ajax library. However, ASP.NET contains some built-in support for asynchronous callbacks. This includes the IsCallback property exposed by the Page object, which indicates if the current request for the page is from a clientside callback.

This simple example shows how you can create client-side asynchronous callbacks using the built-in features of ASP.NET. The example displays a single button that, when clicked, fetches the current time from the server and displays it below the buttonand all without causing a postback to the server (see Figure 9.8).

Figure 9.8. Fetching the time from the server asynchronously


The example page contains the following declaration of a button control. Notice that it is not a server control, and does not cause a postbackit simply calls a client-side JavaScript function named getServerTime. Alternatively, you can use any event that occurs in the client (even an interval timer event) to initiate the background request:

<input type="button" onclick="getServerTime();"        value="Get Server Time"/>


There is a <div> element below the button that displays the result of the callback. Again, this is not a server control:

<div > </div>


Implementing the Server-side Callback Code

Most of the work in implementing the callbacks occurs on the server. The first step is to set up the page to accept callbacks and provide the required features to support them. This means implementing the ICallbackEventHandler interface, which you declare with an Implements directive at the start of the page:

<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>


Now you can create the routine that executes when a callback occurs. The only requirement (and the only member of the ICallbackEventHandler interface) is an event handler named RaiseCallbackEvent that accepts a String and returns a String as the result. In the example page, the routine just returns the current date and time, as shown in Listing 9.13.

Listing 9.13. The Routine That Executes When a Callback Occurs

public String RaiseCallbackEvent(String arg) {   return DateTime.Now.ToString(); }

The next step is to generate the client-side function that the button on the page executes. This function must initiate the callback to the server. To build a suitable statement to achieve this, you can use the GetCallbackEventReference method of the ClientScriptManager instance for the Page. This creates a String like this:

WebForm_DoCallback('__Page',arg,showServerTime,context,null,true);


The arguments are, in order, the following:

  • The object that will receive the callback event

  • Any arguments to be passed to it

  • The name of the client-side function that the server will call to return the results

  • Any context (such as a control name) that you want to provide to the callback method on the server

  • The name of the client-side script function to execute if an error occurs (not specified in this example)

  • Either true for asynchronous background requests or false otherwise

The current ClientScriptManager instance is accessible through the ClientScript property of the Page. The ClientScriptManager class also provides a method named GetPostbackEventReference that does much the same as the GetCallbackEventReference method, but it generates a client-side JavaScript statement that causes a postback to the server rather than a background request.


Listing 9.14 shows the entire Page_Load event handler in the example page. The first line of code generates the client-side callback statement you just saw. The next line creates the client-side code that includes two functions. The first is the getServerTime function that is executed by the button on the page. It includes the statement (generated in the previous line) that causes the callback, so that clicking the button will start the callback to the server.

The second function, named showServerTime (and specified in the call to the GetCallbackEventReference method), is the one that executes after the server-side RaiseCallbackEvent code that generates the result is complete. It gets a reference to the <div> element on the page and inserts the value returned by the server. Finally, the IsClientScriptBlockRegistered method of the ClientScriptManager is executed to see if this client-side script block is already registered with the page (in fact, it never will be because the script block is only ever inserted once on each Page_Load event, but it demonstrates the technique). Providing that it is not already registered, the code then calls the RegisterClientScriptBlock to insert the script block.

Listing 9.14. The Server-Side Page_Load Routine

void Page_Load() {   // generate the client-side script statement that will cause an   // asynchronous server callback to occur. showServerTime is the   // name of the client-side function that will be executed to   // return the values from the server to the client.   String generatedCallback       = Page.ClientScript.GetCallbackEventReference(Page, "arg",                               "showServerTime", "context", true);   // create a String containing the client-side code to insert into   // the page. getServerTime is the routine that will be executed   // when the button on the page is clicked, and it uses the   // callback statement generated in the previous line of code.   // the function showServerTime simply inserts the return value   // into a <div> control on the page.   String clientCallFunction = "function getServerTime(arg, context)\n"       + "{\n"       + " "+ generatedCallback + ";\n"       + "}\n"       + "function showServerTime(result, context)\n"       + "{\n"       + " document.getElementById('divTime').innerText = result;\n"       + "}\n";   // check if the a script block with this name has   // already been registered with the page   if (! Page.ClientScript.IsClientScriptBlockRegistered(                                      "ServerTimeCallbackScript"))   {     // if not, insert the client-side script block just created     Page.ClientScript.RegisterClientScriptBlock(this.GetType(),            "ServerTimeCallbackScript", clientCallFunction, true);   } }

Listing 9.15 shows what the JavaScript code that is generated looks like in the page, for example, when you select View | Source in your browser. This more clearly demonstrates how the callbacks work. Clicking the button on the page executes the getServerTime function, which calls other server-side script routines inserted into the page automatically by ASP.NET. These routines instantiate the XmlHttp object (supported by most modern browsers) and use it to send a request to the server. ASP.NET executes the page up to the PreRender stage and returns only the result of the server-side callback function (named RaiseCallbackEvent). More client-side code then takes this returned value and calls the function you created as a callback (in this example showServerTime), passing to it the result obtained from the server.

Listing 9.15. The Client-Side Script That Is Sent to the Client

<script type="text/javascript"> <!-- function getServerTime(arg, context) {   WebForm_DoCallback('__Page',arg,showServerTime,context,null,true); } function showServerTime(result, context) {   document.getElementById('divTime').innerText = result; } // --> </script>

All the client-side code required to achieve this, and hidden from you, is available because ASP.NET automatically adds a script include reference to the page, like this:

<script src="/books/1/268/1/html/2//IllustratedASPNET20/WebResource.axd?d=n8...23"             type="text/javascript"></script>


It also inserts a start-up script section at the end of the <form> section of the page that initializes the whole process, including making the URL of the current page available to the client-side code and initializing the XmlHttp object (see Listing 9.16).

Listing 9.16. The Client-Side Start-up Script Inserted Automatically by ASP.NET

<script type="text/javascript"> <!-- var pageUrl='/IllustratedASPNET20/ch09/async-callback.aspx?ex=6'; WebForm_InitCallback(); // --> </script>

Even though the client-side callback process looks complicated, it is relatively easy to implementas you have seen in this example. It can provide much more usable and interactive interfaces, and you will find many uses for it in your applications. A good example is the MSDN Web site, as referenced throughout this book, where the left-hand tree view section is populated using client-side callbacks without requiring a page reload each time you make a selection in the tree.



ASP. NET 2.0 Illustrated
ASP.NET 2.0 Illustrated
ISBN: 0321418344
EAN: 2147483647
Year: 2006
Pages: 147

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net