Chapter 4. Partial Page Rendering

The key concept behind AJAX development is that much more work than usual should occur on the client. For the time being, working on the client side also means using a script language, which is not something that would make most developers jump for joy. A large share of Web developers have a love-hate relationship with JavaScript and with script languages in general. Either they love it and can achieve virtually any results by leveraging the flexibility of the syntax, or they hate it and feel ill just at the appearance of a client <script> tag.

By design, any AJAX-enabled page inherently requires a bit of JavaScript code to start a remote operation and refresh the portion of the page affected by the results of the operation. The richer and more sophisticated the AJAX functions, the larger the developer’s exposure is to JavaScript. No AJAX framework can change this basic fact, and ASP.NET AJAX Extensions are no exception.

Given that client script can’t be avoided in AJAX, many frameworks, instead of trying to avoid using it, will hide as many details as possible of the required script. They tend to shield the developer from JavaScript and present a familiar programming model—mostly a server-based programming model. According to this model, server-side components generate required script code and silently inject it into the client page. What does it mean in the context of ASP.NET?

By using a handful of new server controls, ASP.NET developers can easily experience the thrill of using AJAX in their applications and they can do so with a very short learning curve. As you’ll see in this chapter, you need not learn a new programming model or significantly improve your script skills to build effective AJAX applications. At the same time, though, you must be aware that what’s covered in this chapter is only a relatively small portion of the AJAX capabilities. You still need a good grasp of JavaScript and client-side technologies to take full control of the ASP.NET AJAX platform.

Defining Updatable Regions

ASP.NET AJAX Extensions comprise two distinct but not mutually exclusive APIs: client and server. You can build AJAX functionalities using direct client-side programming, traditional server-side programming, or any combination of the two. We’ll focus on server-centric programming features in this chapter and leave the client-side model for Chapter 6, “Built-in Application Services” and Chapter 7, “Remote Method Calls with ASP.NET AJAX.”

As mentioned, any AJAX-based page requires some client-side JavaScript code to do its job; it isn’t necessary, however, to leave the writing of such a script code to the ASP.NET programmer. A framework, in fact, could generate made-to-measure script code as the output of a server-side control. The server control, though, can take a variety of forms. It can be the AJAX version of a traditional server control—for example, an AJAX-enabled drop-down list that supports client insertions and moves them back to the server without a full page postback. It can also be a generic control container that takes care of refreshing all of its children without a full page postback.

As you can see, opposing philosophies underly these two approaches. If you opt for self-enabled AJAX controls, you need a new family of server controls that have a deep impact on existing pages. If you opt for containers instead, you need to learn and use only one new server control with a subsequent limited impact on existing code and your learning curve. ASP.NET AJAX Extensions chooses this second route.

In ASP.NET AJAX, page updates can be governed by a piece of client code automatically injected by a server control—the UpdatePanel control. By wrapping portions of ASP.NET pages in an updatable panel, you automatically transform any postbacks originated by embedded controls in a lightweight AJAX postback. This form of indirect page updating is by far the simplest way to add AJAX capabilities to new and existing ASP.NET 2.0 pages.

Generalities of the UpdatePanel Control

The UpdatePanel control represents the nerve center of the server-centric programming model of ASP.NET AJAX. It groups collections of server controls in updatable panels and ensures that the panel is refreshed using AJAX postbacks. It lets you execute server-side code and return updated markup to the client browser.

You might wonder how this differs from classic postbacks. The difference is in how the postback is implemented—instead of using a full page refresh, the UpdatePanel control sends an out-of-band request for fresh markup and then updates the Document Object Model (DOM) tree when the response is ready. Let’s investigate the programming interface of the control.

The UpdatePanel Control at a Glance

The UpdatePanel control is a container control that enables partial rendering in an ASP.NET page. Defined in the System.Web.Extensions assembly, it belongs specifically to the System.Web.UI namespace. The control class is declared as follows:

public class UpdatePanel : Control
{
   ...
}

Although it’s logically similar to the classic ASP.NET Panel control, the UpdatePanel control differs from the classic panel control in a number of aspects. In particular, it doesn’t derive from Panel and, subsequently, it doesn’t feature the same set of capabilities as ASP.NET panels, such as scrolling, styling, wrapping, and content management.

The UpdatePanel control derives directly from Control, meaning that it acts as a mere AJAX-aware container of child controls. It provides no user interface (UI) related facilities. Any required styling and formatting should be provided through the child controls. In contrast, the control sports a number of properties to control page updates and also exposes a client-side object model.

The Programming Interface of the Control

Table 4-1 details the properties defined on the UpdatePanel control that constitute the aspects of the control’s behavior that developers can govern.

Table 4-1. Properties of the UpdatePanel Control

Property

Description

ChildrenAsTriggers

Indicates whether postbacks coming from child controls will cause the UpdatePanel to refresh. This property is set to true by default. When this property is false, postbacks from child controls are ignored. You can’t set this property to false when the UpdateMode property is set to Always.

ContentTemplate

A template property, defines what appears in the UpdatePanel when it is rendered.

ContentTemplateContainer

Gets the template container object you can use to programmatically add child controls to the UpdatePanel.

IsInPartialRendering

Indicates whether the panel is being updated as part of an asynchronous postback. This property is designed for control developers. (More detail is provided later in this chapter.)

RenderMode

Indicates whether the contents of the panel will be rendered as a block <div> tag or as an inline <span> tag. The feasible values for the property—Block and Inline—are defined in the UpdatePanelRenderMode enumeration. The default is Block. (More detail is provided later in this chapter.)

Triggers

Defines a collection of trigger objects, each representing an event that causes the panel to refresh automatically.

UpdateMode

Gets or sets the rendering mode of the control by determining under which conditions the panel gets updated. The feasible values—Always and Conditional—come from the UpdatePanelUpdateMode enumeration:. The default is Always. (More detail is provided later in this chapter.)

To add child controls programmatically to an updatable panel, you use the ContentTemplateContainer property. In ASP.NET 2.0, to add or remove a child control, you use the Controls property. Why should you use the ContentTemplateContainer property with an UpdatePanel control? The reason is that what you really need to do with the UpdatePanel control is add or remove controls to the content template, not the control directly. That’s why Controls doesn’t work and you have to opt for the actual container of the template.

For example, consider this line of code:

UpdatePanel1.Controls.Add(new LiteralControl("Test"));

If you try to add a child control programmatically to the Controls collection—as in the preceding code snippet—all that you get is a runtime exception.

Note

Note

As mentioned, no ASP.NET control can remove or hide the Controls property, but that doesn’t mean you should use it in the case of the UpdatePanel control. The UpdatePanel control doesn’t need it because UpdatePanel is a templated control. All child controls, therefore, should be added to the template container. Period.

Although such an explanation might be sufficient for novice or non-curious ASP.NET developers, seasoned ASP.NET developers might wonder how it’s possible that you get an exception as soon as you try to add a new element to the Controls collection. For this to happen—they would argue—you need a homemade type of collection. And that’s exactly what happens!

The real type of the object returned through the Controls property is not the default ControlCollection type; rather, it’s an internal SingleChildControlCollection type. The Add method of this collection type throws the exception. In ASP.NET control development, the trick to replacing the object behind the Controls property in a control is overriding the CreateControlCollection method. This is just what UpdateControl does.

Programmatic Updates

In addition to the properties listed in Table 4-1, the UpdatePanel control also features a public method with the following signature:

public void Update()

The method doesn’t take any special action itself, but is limited to requiring that the child controls defined in the content template of the UpdatePanel control be refreshed. By using the Update method, you can programmatically control when the page region is updated in response to a standard postback event or perhaps during the initialization of the page.

An invalid operation exception can be thrown from within the Update method in a couple of well-known situations. One is if you call the method when the UpdateMode property is set to Always. The exception is thrown in this case because a method invocation prefigures a conditional update—you do it when you need it—which is just the opposite of what the Always value of the UpdateMode property indicates. The other situation in which the exception is thrown is when the Update method is called during or after the page’s rendering stage.

So when should you get to use the Update method in your pages? You resort to the method if you have some server logic to determine whether an UpdatePanel control should be updated. As mentioned, this requires that you set the UpdateMode property to Conditional. In addition, if you need the decision of updating the panel to be strictly determined by your server logic, set the ChildrenAsTriggers property to false and make sure no triggers are defined for the panel. We’ll return to this point later in the chapter.

The Eventing Model of the Control

The UpdatePanel control doesn’t fire custom events. The only supported events are those it inherits from the base Control class and that are common to all ASP.NET controls: Init, Load, PreRender, DataBinding, and Unload.

The control defines internal handlers for all these events except DataBinding. During the pre-rendering stage, the control checks for conflicts between the current values of ChildrenAsTriggers and UpdateMode and raises an exception if UpdateMode is Always and ChildrenAsTriggers is set to false.

Your handler for the Init event (as well as handlers for other events) will be invoked before the control does its own scheduled job for the event.

Enabling Partial Rendering

An ASP.NET AJAX page looks almost identical to a normal ASP.NET page, except that it includes a ScriptManager control and one or more UpdatePanel controls. Each copy of the UpdatePanel control refers to a page region that can be updated independently from other regions and the remainder of the page. UpdatePanel controls can also be placed inside user controls and on both master and content pages. In addition, it can be nested inside of another UpdatePanel control and, like any other ASP.NET server control, created and appended dynamically to the page.

Partial rendering divides the page into independent regions, each of which controls its own postbacks and refreshes without causing or requiring a full page update. This is desirable when only a portion—and perhaps only a small portion—of the page needs to change during a postback. Partial updates reduce screen flickering and allow you to create more interactive Web applications. As an AJAX programmer, in fact, you can afford more postbacks than is reasonably possible if the entire page has to be refreshed on each and every postback.

Important

Important

Like any other ASP.NET AJAX feature, partial rendering requires a ScriptManager control in the page. It is essential, though, that the EnablePartialRendering property on the manager be set to true—which is the default case. If the property is set to false, UpdatePanel works like a regular panel.

Defining the Updatable Contents

The contents of the UpdatePanel control are expressed through a template property—ContentTemplate. You typically define the template declaratively, using the <ContentTemplate> element. However, the ContentTemplate property can be set to any object that implements the ITemplate interface, including a Web user control, as shown here:

void Page_PreInit(object sender, EventArgs e)
{
    string ascx = "customerView.ascx";
    UpdatePanel1.ContentTemplate = this.LoadTemplate(ascx);
}

Note that the ContentTemplate property of an UpdatePanel control cannot be set after the template has been instantiated or the content template container has been created. This means that you can safely load the content template only in the PreInit event. If you move the preceding code to the page’s Init or Load handler, you’ll get an exception.

Normally, ASP.NET controls included in a template are not visible at the page level. To get a valid reference to any control in a template, you have to use the FindControl method on the control instance that holds the template. Let’s consider the following code snippet:

<asp:UpdatePanel id="UpdatePanel1" runat="server" ...>
  <ContentTemplate>
    <asp:textbox runat="server" id="TextBox1" ... />
    <asp:button runat="server" id="Button1" onclick="Button1_Click" ... />
    ...
  </ContentTemplate>
</asp:UpdatePanel>

What do you think the following code will do at run time? Will the text box’s value be retrieved or will the assignment throw a null-reference exception?

void Button1_Click(object sender, EventArgs e)
{
   string temp = TextBox1.Text;
}

The TextBox1 control is defined inside a template and, as such, it’s not scoped to the page. In ASP.NET 1.x, the preceding code will just fail; in ASP.NET 2.0, though, it might work fine if the template property is decorated with the TemplateInstance attribute. As you can see in the following code snippet, this is exactly the case for the UpdatePanel’s ContentTemplate property:

 [Browsable(false)]
 [PersistenceMode(PersistenceMode.InnerProperty)]
 [TemplateInstance(TemplateInstance.Single)]
public ITemplate ContentTemplate
{
   get { return _contentTemplate; }
   set { _contentTemplate = value; }
}

In the end, any control defined in the ContentTemplate property can be accessed directly from any event handler defined within the host page.

Defining Regions Programmatically

The ContentTemplate property lets you define the contents of the updatable region in a declarative manner. What if you need to edit the template programmatically and add or remove controls dynamically? The following template defines a button and a label:

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <asp:Button runat="server" ID="Button1"
            Text="What time is it?" OnClick="Button1_Click" />
        <br />
       <asp:Label ID="Label1" runat="server" Text="[time]" />
    </ContentTemplate>
</asp:UpdatePanel>

The button is associated with a Click event handler that sets the label to the current time:

protected void Button1_Click(object sender, EventArgs e)
{
    Label1.Text = DateTime.Now.ToShortTimeString();
}

As you click the button, the page posts back and updates the label. You author the page in the traditional way. When controls you drag and drop onto the designer’s surface are hosted in an updatable panel, however, those controls interact with the server using AJAX postbacks instead of classic whole-page postbacks. We’ll return to the mechanics of AJAX postbacks in a moment. Figure 4-1 shows the page in action. The page works as expected, except that there is no indication on the browser’s status bar signals that a full page postback is taking place.

A sample ASP.NET AJAX page in action

Figure 4-1. A sample ASP.NET AJAX page in action

To add controls to or remove controls from an updatable panel, you use the ContentTemplateContainer property. The following code shows how to build the same user interface shown in Figure 4-1, but in this case creating it programmatically. In addition to defining the template on the fly, the following code also adds the UpdatePanel control dynamically to the page via code:

public partial class Samples_Ch04_Simple_Dynamic : System.Web.UI.Page
{
    private Label Label1;

    protected void Page_Load(object sender, EventArgs e)
    {
        UpdatePanel upd = new UpdatePanel();
        upd.ID = "UpdatePanel1";

        // Define the button
        Button button1 = new Button();
        button1.ID = "Button1";
        button1.Text = "What time is it?";
        button1.Click += new EventHandler(Button1_Click);

        // Define the literals
        LiteralControl lit = new LiteralControl("<br>");

        // Define the label
        Label1 = new Label();
        Label1.ID = "Label1";
        Label1.Text = "[time]";

        // Link controls to the UpdatePanel
        upd.ContentTemplateContainer.Controls.Add(button1);
        upd.ContentTemplateContainer.Controls.Add(lit);
        upd.ContentTemplateContainer.Controls.Add(Label1);

        // Add the UpdatePanel to the list of form controls
        this.Form.Controls.Add(upd);
    }

    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = DateTime.Now.ToShortTimeString();
    }
}

You can add an UpdatePanel control to the page at any time in the life cycle. Likewise, you can add controls to an existing panel at any time. However, you can’t set the content template programmatically past the page’s PreInit event.

Rendering Modes

The contents of the panel can be merged with the host page in either of two ways: as inline markup or as block-level elements. You control this setting through the RenderMode property. The feasible values for the property are listed in Table 4-2.

Table 4-2. The UpdatePanelRenderMode Enumeration

Value

Description

Block

The contents of the panel are enclosed by a <div> element.

Inline

The contents of the panel are enclosed by a <span> element.

By default, the contents of the panel form a new block and are wrapped by a <div> tag, as demonstrated by the following sample code:

<div id="UpdatePanel1">
   ...
</div>

If you opt for the Inline option, the contents flow with the page, as you can see in the following example:

<form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1" />

    <big>This panel has been generated (inline) at:
        <asp:UpdatePanel ID="UpdatePanel1" runat="server"
             RenderMode="inline">
            <ContentTemplate>
                <b style="background-color:lime">
                <% =DateTime.Now %>
                </b>.<hr />
                <asp:Button ID="Button1" runat="server" Text="Refresh" />
            </ContentTemplate>
        </asp:UpdatePanel>
    </big>

    <br /><br /><br />

    <big>This panel has been generated (block) at:
        <asp:UpdatePanel ID="UpdatePanel2" runat="server"
             RenderMode="block">
            <ContentTemplate>
                <b style="background-color:lime">
                <% =DateTime.Now %>
                </b>.<hr />
                <asp:Button ID="Button2" runat="server" Text="Refresh" />
            </ContentTemplate>
        </asp:UpdatePanel>
    </big>
</form>

The preceding code produces output like that shown in Figure 4-2.

Using an UpdatePanel control to render inline text

Figure 4-2. Using an UpdatePanel control to render inline text

The updatable part of the page might not be a block of markup; it can also be simply an inline fragment of a paragraph.

Detecting Ongoing Updates

During a postback, the IsInPartialRendering read-only Boolean property indicates whether the contents of an UpdatePanel control are being updated. You access the property during postback code and the property returns true if the current request is being executed as a result of an AJAX postback.

Note that the property is designed for use by control developers only. Marked as a public member, the property can be used from the code-behind class of a page; however, it will always return false and is nearly useless at that level. The property is assigned its proper value only at rendering time. If used in the rendering stage of a custom control that inherits from UpdatePanel, it has the expected value when you render the markup for the panel.

If used from within the page, the property is set to its expected value only after the page markup has been rendered—for example, in the page Unload event or in the override of the Render method. This is good for tracing purposes; however, it’s too late if you need the property to modify the output.

protected override void Render(HtmlTextWriter writer)
{
   // This call assigns the proper value to the
   // IsInPartialRendering property.
   base.Render(writer);

   // From now on, the IsInPartialRendering property
   // has the expected value. It's too late, though,
   // to update the page markup.
   Trace.WriteLine("The update panel {0} partially rendering",
      updatePanel1.IsInPartialRendering ? "is" : "is not");
   ...
}

An UpdatePanel control can be nested inside other UpdatePanel controls. If the parent panel refreshes for whatever reason (a trigger, child postback, or unconditional refresh), the value of IsInPartialRendering for the nested panel is nearly always false. The property value for the parent is true if the panel is truly partially rendering. Nested panels are always refreshed if the parent is.

The value of IsInPartialRendering for a nested panel is true only if the panel is being refreshed and the parent panel has UpdateMode set to Conditional (which not the default case). We’ll return to conditional updates later in the chapter.

Consider the following code with two nested panels, the output of which is shown in Figure 4-3:

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <b style="background-color:lime">
        <% =DateTime.Now %>
        </b>
        <hr />
        <div>
            <big>The innermost panel has been last updated at:
              <asp:UpdatePanel ID="UpdatePanel2" runat="server">
                <ContentTemplate>
                  <b style="background-color:yellow">
                  <% =DateTime.Now %>
                  </b>
                  <hr />
                  <asp:Button ID="Button2" runat="server" Text="Refresh" />
                </ContentTemplate>
              </asp:UpdatePanel>
            </big>
        </div>
        <asp:Button ID="Button1" runat="server" Text="Refresh" />
    </ContentTemplate>
</asp:UpdatePanel>
Nested UpdatePanel controls

Figure 4-3. Nested UpdatePanel controls

If you just need to know whether a portion of a page is being updated as a result of an AJAX postback, you use the IsInAsyncPostBack Boolean property on the ScriptManager control.

Testing the UpdatePanel Control

At this point, you know all the basic facts about updatable panels. Before we take the plunge into more advanced and powerful features, let’s consider a couple of data-driven examples.

A First Sample Page

Many ASP.NET controls contain clickable elements such as button and links. As the user interacts with the markup of any of these controls, she clicks and causes the page to post back and refresh entirely. Grid controls such DataGrid and GridView are the perfect fit. These controls can generate pages of output, and the user needs to click and post back to see the contents of a given page. Is there a way to have grid controls provide a paged, sorted, or filtered view of their contents without fully refreshing the host page?

Let’s suppose your page has a grid with some data in it for users to page through. The following listing illustrates this scenario as you would code it for a classic ASP.NET page:

<asp:GridView ID="GridView1" runat="server"
     DataSourceID="ObjectDataSource1" AllowPaging="True"
     AutoGenerateColumns="False">
    <Columns>
        <asp:BoundField DataField="ID" HeaderText="ID" />
        <asp:BoundField DataField="CompanyName" HeaderText="Company" />
        <asp:BoundField DataField="Country" HeaderText="Country" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
     TypeName="IntroAjax.CustomerManager"
     SelectMethod="LoadAll" />

Every time the user clicks to view a new page, the page posts back and gets fully refreshed. For a page that contains a lot of other content, this might be a serious issue. With ASP.NET AJAX, you can isolate the portion of the page that needs updates and wrap it in an UpdatePanel control. The net effect is that only the contents of the panel are updated as the user clicks. Here’s the AJAX-enabled version of the preceding page:

<asp:ScriptManager ID="ScriptManager1" runat="server" />

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
   <ContentTemplate>
      <asp:GridView ID="GridView1" runat="server"
           DataSourceID="ObjectDataSource1" AllowPaging="True"
           AutoGenerateColumns="False">
         <Columns>
            <asp:BoundField DataField="ID" HeaderText="ID" />
            <asp:BoundField DataField="CompanyName" HeaderText="Company" />
            <asp:BoundField DataField="Country" HeaderText="Country" />
         </Columns>
      </asp:GridView>
      <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
           TypeName="IntroAjax.CustomerManager"
           SelectMethod="LoadAll" />
   </ContentTemplate>
</asp:UpdatePanel>

Basically, any updatable contents have been moved to the <ContentTemplate> section of the UpdatePanel control and a ScriptManager control has been added. The changes are limited, but the final effect is significantly better.

The postback is generated as usual and by the usual server controls. As a result, only the page fragment embedded in the UpdatePanel control is updated. The markup for the page segment incorporated in the UpdatePanel control is regenerated on the server as a result of the AJAX postback. That markup is pasted to an HTTP response and sent back to the client. Next, the AJAX infrastructure takes care of it and updates the page. We’ll delve deep into this mechanism in a moment.

Note

Note

In ASP.NET 2.0, the GridView and DetailsView controls come with a Boolean property named EnableSortingAndPagingCallbacks. If set, the property causes the controls to employ a client-side callback for sorting and paging operations. The contents for the host page are retrieved on the server, but the control’s markup is updated without fully refreshing the page. The same feature is now replicated by a far larger framework, the ASP.NET AJAX Extensions. It is not recommended, though, that you use sorting and paging callbacks (or any other sort of ASP.NET Script Callback features) along with ASP.NET AJAX Extensions and specifically with updatable panels. The risk is that concurrent calls hit the server resulting in a possibly inconsistent view state for the page.

Using AJAX for Multistep Operations

Wizards and rich controls that support features such as in-place editing, collapse/expand, and multiview facilities are perfect for AJAX pages. By using updatable panels, you only have to wrap the old classic server code in an UpdatePanel control. Figure 4-4 shows a sample page obtained by combining a DetailsView (for display) and a SqlDataSource control (for data editing).

Free AJAX-powered in-place editing

Figure 4-4. Free AJAX-powered in-place editing

You can navigate through the records and click to edit the various fields. Whatever postback operation is required in this context is limited to the markup around the DetailsView control. Everything else in the page is left intact and is not included in the HTTP packets.

Note

Note

All in all, the most interesting trait of this example is not the functionality itself, but rather the fact that you can virtually host any combination of ASP.NET controls in an updatable panel. If any of these controls cause postbacks, you can have the affected segment of the page updated independently from the rest of the page. And no special knowledge of the ASP.NET AJAX framework is required to code this.

Compared to the EnableSortingAndPagingCallbacks property, an UpdatePanel control might look like its double at first. However, it’s much more flexible than that property and addresses a far broader range of scenarios.

Using AJAX for Quicker Data Validation

Input forms of any kind require some sort of data validation. ASP.NET validator controls do a good job both on the client (via JavaScript) and the server side. A client-side validation layer might save you from a number of postbacks, but a server-side validation layer is always necessary if for only for security reasons. If you rely on client script checks only, your entire validation infrastructure might be cut off by disabling JavaScript altogether in the client browser. For this reason, server validation, and subsequently postbacks, are required.

There are also situations in which data can’t just be validated on the client. For example, consider the following code:

<table>
    <tr>
        <td>User Name</td><td>*</td>
        <td><asp:textbox runat="server" id="userName" />
            <asp:RequiredFieldValidator runat="server"
                id="userReqValidator"
                ControlToValidate="userName"
                Text="!!!"
                ErrorMessage="User name is mandatory" />
            <asp:CustomValidator runat="server" id="userValidator"
                OnServerValidate="CheckUserName"
                ControlToValidate="userName"
                ErrorMessage="The proposed name already exists or is not
                              valid." /></td></tr>
    <tr>
        <td>Email</td><td>*</td>
        <td><asp:textbox runat="server" id="email" />
            <asp:RequiredFieldValidator runat="server"
                id="emailReqValidator"
                ControlToValidate="email"
                Text="!!!"
                ErrorMessage="Membership is mandatory" />
            <asp:RegularExpressionValidator runat="server"
                id="emailValidator"
                ControlToValidate="email"
                ValidationExpression="[a-zA-Z_0-9.-]+@[a-zA-Z_0-9.-
                                       ]+.w+"
                ErrorMessage="Must be an email address." /></td></tr>
    <tr>
        <td>Membership Level</td><td>*</td>
        <td><asp:textbox runat="server" id="membership" />
            <asp:RequiredFieldValidator runat="server"
                id="membershipReqValidator"
                ControlToValidate="membership"
                Text="!!!"
                ErrorMessage="Membership is mandatory" />
            <asp:CustomValidator runat="server" id="membershipValidator"
                OnServerValidate="CheckMembership"
                ControlToValidate="membership"
                ErrorMessage="Must be Gold or Platinum." /></td></tr>
</table>

The form contains three mandatory input fields: user name, e-mail, and membership. The user name must be unique, but the uniqueness of the typed value can’t be verified on the client. In contrast, the format of the e-mail address can be easily validated through a regular expression on the client. If you also want to check whether the e-mail address exists, you need server code.

The CustomValidator control is an ASP.NET validator control that you use to validate input on the server. You specify the validation code through the ServerValidate server event. Here’s an example:

protected void CheckUserName(object source, ServerValidateEventArgs e)
{
    e.IsValid = false;
    string buf = e.Value.ToLower();

    // Deny all names starting with "dino"
    if (!buf.StartsWith("dino"))
        e.IsValid = true;
}

For this code to run, a postback is required. With AJAX, you can go back to the server without a full page refresh. The final effect is similar to client-side validation. (See Figure 4-5.)

Page validation made with AJAX

Figure 4-5. Page validation made with AJAX

To trigger the postback, the user needs to click a button unless you turn on the AutoPostBack attribute on an input control:

<asp:textbox runat="server" id="userName" AutoPostBack="true" />

For this example, the validation process is automatically triggered as soon as the user tabs out of the field. In this case, you also need to complete the Page_Load event handler with the following code:

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack)
        this.Validate();
}

Note

Note

A scenario in which it would seemingly be interesting to use AJAX functionality is when uploading files. To do the job, in ASP.NET you either use the FileUpload Web control or the HtmlInputFile HTML control.

The way in which the control is rendered to HTML is browser specific, but it normally consists of a text box and a Browse button. The user selects a file from the local machine and then clicks the button to submit the page to the server. When this occurs, the browser uploads the selected file to the server. The UpdatePanel control knows how to capture the submit event of the posting control, but it doesn’t upload the file. As a result, on the server there’s no uploaded file to save anywhere.

This behavior is by design. A FileUpload control can be used in the same page as an UpdatePanel control as long as you place it outside of the panel’s content template.

Master Pages and Partial Updates

You can safely use UpdatePanel controls from within master pages. Most of the time, the use of updatable panels is easy and seamless. There are a few situations, though, that deserve a bit of further explanation.

If you add a ScriptManager control to a master page, partial rendering is enabled by default for all content pages. In addition, initial settings on the script manager are inherited by all content pages. As we saw in Chapter 3, “Adding AJAX Capabilities to a Site,” you can use the ScriptManagerProxy control in the content page to reference the parent manager and change some of its settings. The ScriptManagerProxy control, though, is mostly designed to let you edit the list of scripts and services registered with the manager in a declarative manner, and it doesn’t let you customize, say, error handling. You can do the same (and indeed much more) by programmatically referencing the script manager in the master page. Here’s how:

// Reference to the script manager of a content page.
// The script manager is defined on the master page.
ScriptManager sm = ScriptManager.GetCurrent(this);

In some cases, a content placeholder on the master page might be wrapped in an updatable region. As the author of the content page, you have no control over that. What if you’re going to use in that page a control that doesn’t work with UpdatePanel, such as with file uploads? In this case, you need to disable partial rendering just for that individual content page. Here’s how you can do it:

protected void Page_Init(object sender, EventArgs e)
{
    ScriptManager.GetCurrent(this).EnablePartialRendering = false;
}

In the content page, you create a handler for the page’s Init event that sets EnablePartialRendering on the script manager property to false. You must change the state of the EnablePartialRendering property during or before the content page’s Init event. You can’t accomplish this task just by using the ScriptManagerProxy control.

Partial Updates from User Controls

User controls provide an easy way to bring self-contained, auto-updatable AJAX components into an ASP.NET page. Because each page can have at most one script manager, you can’t reasonably place the script manager in the user control. That would work and make the user control completely self-contained, but it would also limit you to using exactly one instance of the user control per page. On the other hand, the UpdatePanel control requires a script manager on the page containing the UpdatePanel. Multiple script managers or the lack of at least one script manager cause an exception.

The simplest workaround is that you take the script manager out of the user controls and place it in the host page. User controls assume the presence of a script manager, and they use internally as many updatable panels as needed. Figure 4-6 shows an example.

Two AJAX-enabled user controls in the same page

Figure 4-6. Two AJAX-enabled user controls in the same page

<asp:ScriptManager runat="server" ID="ScriptManager1" />
<x:Clock runat="server" ID="Clock1" />
<hr />
<x:Clock runat="server" ID="Clock2" />

Warning

Warning

You can’t call Response.Write from within a postback event handler (for example, Button1_Click) that gets called during an asynchronous AJAX postback. If you do so, you’ll receive a client exception stating that the message received from the server could not be parsed. In general, calls to Response.Write—but also response filters, HTTP modules, or server tracing (Trace=true)—modify the stream returned to the client by adding explicit data that alters the expected format.

The Mechanics of Updatable Panels

The magic of updating specific portions of the page springs from the combined efforts of the UpdatePanel control and the AJAX script manager component. During the initialization phase, the UpdatePanel control gets a reference to the page’s script manager and registers with it. In this way, the script manager knows how many updatable panels the host page contains and holds a reference to each of them.

The script manager also emits proper client script code to intercept any postback submit actions started on the client. Such a script code hooks the form’s submit event and replaces the standard data-submission process with an AJAX asynchronous call that is run through the XMLHttpRequest object. Let’s step through more details.

Client Script Injection

The following fragment shows the typical script emitted in the client page when partial rendering is enabled:

<script type="text/javascript">
//<![CDATA[
Sys.WebForms.PageRequestManager._initialize(
    'ScriptManager1', document.getElementById('form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls(
    ['tUpdatePanel1'], [], [], 90);
//]]>
</script>

As you saw in Chapter 2, “The Microsoft AJAX Client Library,” PageRequestManager is one of the classes that form the client library of ASP.NET AJAX. In particular, the class is in charge of running asynchronous calls through the XMLHttpRequest object.

Note

Note

The _initialize and _updateControls methods are conventionally considered private members of the PageRequestManager class; yet they are externally callable. In Chapter 2, we learned that classes in the Microsoft Client Library (MCL) can’t really have private members. By convention, private members have their names prefixed with the underscore (_). However, this doesn’t prevent code from calling into the underscored methods when necessary.

The _initialize method wires up new event handlers for the form’s submit and click events and for the window’s unload event. The _updateControls method takes four parameters: the list of updatable panels in the form; two arrays with the ID of form controls capable of firing AJAX and classic postbacks, respectively; and the time, in seconds, allowed for the update to complete before timing out. (The default is 90 seconds.) You can take a look at the real implementation of the method by scanning the source code contained in the file MicrosoftAjaxWebForms.js.

Format of the AJAX Request

So the _initialize method prepares a new AJAX request for each classic postback that is going to take place. Manipulated by PageRequestManager’s script, the body of an example GridView update request sent to the server looks much like this:

ScriptManager1=UpdatePanel1|GridView1&
__EVENTTARGET=GridView1&
__EVENTARGUMENT=Page%243&
__VIEWSTATE=%2Fw ... 3D&
__EVENTVALIDATION=%2Fw ... 3D

The original request is the one sent over by a GridView control named GridView1 when the user clicks to view page #3. Note that the %24 character is the HTML-encoded format for the dollar symbol ($).

In addition to the view-state and event-validation data (which are specific features of ASP.NET 2.0 postbacks), the request contains the ID of the updatable panel that will serve the call (UpdatePanel1 in the snippet just shown) and the server control target of the postback (in this case, GridView1). The actual format of the request might change a bit depending on the characteristics of the posting control. For example, GridView uses the __EVENTTARGET field to pass its name to the server. A Button control, on the other hand, uses a new parameter, as shown here:

ScriptManager1=UpdatePanel1|Button1&
__EVENTTARGET=&
__EVENTARGUMENT=&
__VIEWSTATE=%2Fw ... 3D&
__EVENTVALIDATION=%2Fw ... 3D&
Button1=Refresh

You can look at the bits of the request either by using an ad hoc network monitor tool or by simply adding some debug code to the Page_Load event handler:

protected void Page_Load(object sender, EventArgs e)
{
     // Need permissions to write
     Request.SaveAs(@"c:
eq_details.txt", true);
}

In case of anomalies and malfunctions, it’s crucial to analyze the response of an AJAX call. For this reason, you need to provide yourself with a tool that monitors HTTP traffic. Fiddler is certainly a good one. You can read all about its features at http://www.fiddlertool.com/fiddler. It works as a proxy and logs all HTTP traffic so that you can later examine the contents of each request and response. Fiddler supports Internet Explorer as well as other browsers.

Developed by a member of the ASP.NET AJAX team, the Web Development Helper utility is a free tool specifically for Internet Explorer. You can download it from http://www.nikhilk.net/Project.WebDevHelper.aspx. The utility allows viewing information about the current ASP.NET page, such as view state and trace output. In addition, it can also perform some operations on the server, such as restarting the application or managing the Cache object. Finally, the utility provides developers with the ability to view the live HTML DOM and allows for monitoring requests and responses for diagnostic scenarios. Most features of the tool work only when the application is hosted by the local host. Installing the tool requires a bit of manual work, as it is implemented as an Internet Explorer browser helper object. Figure 4-7 shows the tool in action. Note that after installing the tool, you display it by using the View|Explorer Bar menu in Internet Explorer.

The Web Development Helper tool in action

Figure 4-7. The Web Development Helper tool in action

Detecting AJAX Requests at Runtime

On the server, the postback is processed as usual, and indeed there’s no way for the ASP.NET runtime to detect it’s an AJAX call to refresh only a portion of the original page.

More precisely, each AJAX request contains an extra header that is missing in traditional postback requests. However, only ad hoc additional runtime components (for example, HTTP modules)—not the default set of runtime components—can take advantage of this. Here are the details of the new request header:

x-microsoftajax: Delta=true

Figure 4-8 shows the extra header using the user interface of the Web Development Helper tool.

The extra request header that decorates ASP.NET AJAX requests sent through the UpdatePanel control

Figure 4-8. The extra request header that decorates ASP.NET AJAX requests sent through the UpdatePanel control

The header is added only for requests managed through the UpdatePanel control. Other AJAX requests—for example, requests for Web service methods—don’t include the header. These requests, though, have a special content type: application/json.

To detect an AJAX request from the page life cycle, you just use the IsInAsyncPostBack property on the ScriptManager control.

Format of the AJAX Response

From within the page life cycle, the script manager hooks up the standard rendering process by registering a render callback method. The modified rendering process loops through all the registered updatable panels and asks each to output its fresh markup.

At the end of the request, the client is returned only contents of updatable regions, and a JavaScript callback function buried in the client library takes care of replacing the current content of regions with any received markup. Here’s the typical response of an AJAX UpdatePanel response:

2735|updatePanel|UpdatePanel1|
...
-- markup for all updated panels goes here --
...
|0|hiddenField|__EVENTTARGET|
|0|hiddenField|__EVENTARGUMENT|
|684|hiddenField|__VIEWSTATE|5A ... /0=
|64|hiddenField|__EVENTVALIDATION|H2 ... /0=

The output is made of a sequence of records. Each record contains the overall size, a keyword that identifies the client container of the following information, optionally the name of the container (control, variable), and the actual content. Two consecutive records (and fields inside a single record) are separated with pipe characters (|). In the preceding code snippet, more than 2 KB of information are being returned as the replacement for the contents of UpdatePanel control named UpdatePanel1. In addition, the response includes empty __EVENTTARGET and __EVENTARGUMENT hidden fields, plus updated view-state and event-validation data. The response also includes other helper records that cache parameters for the partial update (basically, the input data for the _initialize method), the action URL, and the new page title.

The updated markup constitutes the lion’s share of the response, followed by the updated view-state and event-validation data. All remaining data rarely adds up to an extra KB of information.

One Partial Update at a Time

From the user perspective, a partial page update is an asynchronous operation, meaning that the user can continue working with the controls page and animation can be displayed to entertain the user during a lengthy task and to provide feedback. Two partial updates, though, can’t run together and concurrently.

If you trigger a second partial update when another one is taking place, the first call is aborted to execute the new one. As a result, just one partial update can be executed at a time. This limitation is by design because each partial update updates the view state and event-validation data. To ensure consistency, each AJAX postback must find and return a consistent view state for the next postback to start.

As we’ll see later in the chapter, an updatable region can be bound to events fired by the PageRequestManager client object that signal key steps in the client life cycle of an update such as begin/end request, page loaded, and the like. By handling these events, you can cancel additional postback requests while another one—with a higher priority—is going on. I’ll say more about this later.

Error Handling in Partial Updates

As discussed in Chapter 3, any errors that occur during a partial update are captured by the script manager control and handled in a standard way. The script manager swallows the real exception and sends back to the client a response record similar to the following:

53|error|500|Object reference not set to an instance of an object.|

The first piece of information is the size of the record, followed by a keyword that qualifies the information. Next, you get the error code and the message for the user. From a pure HTTP perspective, the packet received denotes success—HTTP status code 200. The client-side script, though, knows how to handle an error response and by default pops up a message box with the message received.

As in Chapter 3, you can customize the error handling both on the server and the client. On the server, you can hook up the AsyncPostBackError event on the ScriptManager control and set a custom error message through the AsyncPostBackErrorMessage property:

void AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
{
    ScriptManager1.AsyncPostBackErrorMessage = "An error occurred:
" +
        e.Exception.Message;
}

You don’t need to write such an event handler if you just want to return a generic and static message whatever the exception raised. In this case, you simply set the property to the desired string in Page_Load.

On the client, you can register a handler for the endRequest event of the PageRequestManager object, do your custom client handling, and then state that you’re done with the error:

<script type="text/javascript" language="JavaScript">
function pageLoad()
{
    // Handle the endRequest event for partial updates
    var manager = Sys.WebForms.PageRequestManager.getInstance();
    manager.add_endRequest(endRequest);
}
function endRequest(sender, args)
{
    // Do your own error handling here
    $get("Label1").innerHTML = args.get_error().message;

    // State that you're done with the error handling. This statement
    // prevents the standard client-side handling (a message box)
    args.set_errorHandled(true);
}
</script>

The endRequest client event fires at the end of a partial page refresh operation.

Taking Control of Updatable Regions

So far, I’ve considered only pages with a single UpdatePanel control and silently assumed that any updatable panel had to refresh on every postback. It’s more realistic, however, to expect that you’ll have pages with multiple panels. In addition, the updating of panels will likely be triggered by controls that are scattered in the page and not necessarily placed inside the panel. Finally, it’s common to have each panel logically bound to a well-known set of triggers—button clicks, value changes, or HTML events—and refresh only if any of the assigned triggers are active. Let’s see how to take full control of the page update process and assign triggers, conditionally update panels, and use other, more advanced features.

Triggering the Panel Update

In all the examples I’ve illustrated thus far, the control that causes the asynchronous AJAX postback has always been part of the updatable panel and defined within the <ContentTemplate> section. This arrangement is not what you will see in all cases. In general, a panel update can be triggered by any page controls that have the ability to post back. Furthermore, an updatable panel also can be refreshed programmatically when proper runtime conditions are detected during a partial page update caused by another panel.

By default, an UpdatePanel control refreshes its contents whenever a postback occurs within the page. If the control that posts back is placed outside the updatable panel, a full refresh occurs. Otherwise, only the panel that contains the control refreshes. You can programmatically control under which conditions the contents of the panel are refreshed. You have three tools to leverage: the UpdateMode, ChildrenAsTriggers, and Triggers properties.

Updating Modes

Through the UpdateMode property, the UpdatePanel control supports two update modes: Always (the default) and Conditional. The values are grouped in the UpdatePanelUpdateMode enumeration, listed in Table 4-3.

Table 4-3. The UpdatePanelUpdateMode Enumeration

Value

Description

Always

The panel is updated on each postback raised by the page.

Conditional

The panel is updated only when the update is triggered by one of the assigned panel triggers or the update is requested programmatically.

When the UpdateMode property is set to Always, each postback that originates around the page—from controls inside and out of the panel—triggers the update on the panel. This means that if you have multiple updatable panels in a page, all of them will update even though the event concerns only one of them or even none of them.

When the UpdateMode property is set to Conditional, the panel is updated only when you call the Update method on the control during a postback or when a trigger associated with the UpdatePanel control is verified. The ChildrenAsTriggers Boolean property (which is true by default) defines whether children of an updatable panel also trigger a refresh.

We’ll also refer to children controls as “implicit triggers” and triggers defined to through the Triggers property as “explicit triggers.”

Conditional Refresh of an UpdatePanel Control

An UpdatePanel trigger defines a runtime condition—mostly a control event—that causes an UpdatePanel control to be refreshed when the page is working in partial rendering mode. Triggers make sense mostly when the panel is being updated conditionally or when children are not meant to be implicit triggers.

ASP.NET AJAX Extensions supports two types of triggers, both derived from the abstract class UpdatePanelControlTrigger. They are AsyncPostBackTrigger and PostBackTrigger. You associate triggers with an UpdatePanel control declaratively through the <Triggers> section or programmatically via the Triggers collection property. Here’s an example of asynchronous triggers in a page with two updatable panels:

Query string:<br />
<asp:TextBox ID="TextBox1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Load ..."
     OnClick="Button1_Click" />

<asp:UpdatePanel runat="server" ID="UpdatePanel1" UpdateMode="Conditional">
   <ContentTemplate>
      <small>Grid contents generated at: <%=DateTime.Now %></small>
      <asp:GridView ID="GridView1" runat="server"
           DataSourceID="ObjectDataSource1">
         ...
      </asp:GridView>
   </ContentTemplate>
   <Triggers>
      <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
   </Triggers>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional">
   <ContentTemplate>
      <div style="background-color:Lime">
         <big>This panel has been generated at: <%=DateTime.Now %></big>
      </div>
   </ContentTemplate>
   <Triggers>
      <asp:AsyncPostBackTrigger ControlID="Button2" EventName="Click" />
   </Triggers>
</asp:UpdatePanel>

<asp:Button ID="Button2" runat="server" Text="Refresh time"
     OnClick="Button2_Click" />

The user types a query string in the text box that will be used to filter the customers in the grid. (See Figure 4-9.)

The contents of the grid are refreshed only when the user sets a filter

Figure 4-9. The contents of the grid are refreshed only when the user sets a filter

More precisely, the contents of the grid are refreshed when the user pages through the record and when the user clicks the Load button. Paging refreshes the grid as long as ChildrenAsTriggers is set to true—the default—because the link buttons used to page are child controls of the UpdatePanel. The Click event of the Load button, conversely, is registered as an asynchronous postback trigger because it is external to the panel.

As you can see in Figure 4-9, the time at which the grid was last refreshed doesn’t coincide with the time rendered in the second panel. According to the preceding code, to refresh the bottom panel you have to click the Refresh time button—a trigger for the UpdatePanel2 control.

Programmatic Updates of Panels

What if while refreshing the first panel you realize you need to update a second one? You can programmatically command a panel update by using the following code:

protected void Button1_Click(object sender, EventArgs e)
{
   // Do as usual assuming we're refreshing UpdatePanel1
   ...

   // Command an update on an external UpdatePanel control
   UpdatePanel2.Update();
}

You should resort to this approach only if some sort of implicit dependency exists between two panels. In this case, when you are in the process of updating one, there might be conditions that require you to update the second one also. Because at this point your code is executing on the server, there’s no way for you to do this other than by explicitly invoking the Update method on the panel.

Triggers of an UpdatePanel Control

As mentioned, there are two types of triggers for an UpdatePanel control—AsyncPostBackTrigger and PostBackTrigger. They have nearly the same syntax; both, when raised, cause the contents of the UpdatePanel control to be updated during a postback. Where’s the difference between the two? It’s indicated by the name, actually.

The event associated with the AsyncPostBackTrigger component triggers an asynchronous AJAX postback on the UpdatePanel control. As a result, the host page remains intact except for the contents of the referenced panel and its dependencies, if any. Usually, the AsyncPostBackTrigger component points to controls placed outside the UpdatePanel. However, if the panel has the ChildrenAsTriggers property set to false, it could make sense that you define an embedded control as the trigger. In both cases, when a control that is a naming container is used as a trigger, all of its child controls that cause postbacks behave as triggers.

You add an event trigger declaratively using the <Triggers> section of the UpdatePanel control:

<asp:UpdatePanel runat="server" ID="UpdatePanel1">
   <ContentTemplate>
      ...
   </ContentTemplate>
   <Triggers>
      <asp:AsyncPostBackTrigger ControlID="DropDownList1"
           EventName="SelectedIndexChanged" />
   </Triggers>
</asp:UpdatePanel>

You need to specify two pieces of information: the ID of the control to monitor, and the name of the event to catch. Note that the AsyncPostBackTrigger component can catch only server-side events fired by server controls. Both ControlID and EventName are string properties. For example, the panel described in the previous code snippet is refreshed when any of the controls in the page post back or when the selection changes on the drop-down list control named DropDownList1.

It should be noted that in no way does the panel refresh when a client event (i.e., onblur, onchange, click) fires. If you need to refresh when the selection on a list changes, either you set the AutoPostBack property on the control to true so that a key client event fires the postback or you wait for something else around the page to trigger the postback. For example, imagine you have UpdatePanel1 like in the snippet above and UpdatePanel2 bound to a button. When the user simply changes the selection on the drop-down list nothing happens. However, when another panel is refreshed—say, UpdatePanel2, even completely unrelated to the other—then the page places an AJAX postback and the page lifecycle is started for all controls in the page. At this point, the change in the drop-down list is detected and UpdatePanel1 is refreshed too.

Full Postbacks from Inside Updatable Panels

By default, all child controls of an UpdatePanel that post back operate as implicit asynchronous postback triggers. You can prevent all of them from posting by setting ChildrenAsTriggers to false. Note that when ChildrenAsTriggers is false, postbacks coming from child controls are just ignored. In no way are such postback events processed as regular form submissions. You can then re-enable only a few child controls to post back by adding them to the <Triggers> section of the UpdatePanel. These postbacks, though, will only refresh the panel.

There might be situations in which you need to perform full, regular postbacks from inside an UpdatePanel control in response to a control event. In this case, you use the PostBackTrigger component, as shown here:

<asp:UpdatePanel runat="server" ID="UpdatePanel1">
   <ContentTemplate>
      ...
   </ContentTemplate>
   <Triggers>
      <asp:AsyncPostBackTrigger ControlID="DropDownList1"
           EventName="SelectedIndexChanged" />
      <asp:PostBackTrigger ControlID="Button1" />
   </Triggers>
</asp:UpdatePanel>

The preceding panel features both synchronous and asynchronous postback triggers. The panel is updated when the user changes the selection on the drop-down list; the whole host page is refreshed when the user clicks the button.

Note

Note

When should you use a PostBackTrigger component to fire a full postback from inside an updatable panel? Especially when complex and templated controls are involved, it might not be easy to separate blocks of user interface in distinct panels and single controls. So the easiest, and often the only, solution is wrapping a whole block of user interface in an updatable panel. If a single control in this panel needs to fire a full postback, you need the PostBackTrigger component.

A PostBackTrigger component causes referenced controls inside an UpdatePanel control to perform regular postbacks. These triggers must be children of the affected UpdatePanel.

The PostBackTrigger object doesn’t support the EventName property. If a control with that name is causing the form submission, the ASP.NET AJAX client script simply lets the request go as usual. The ASP.NET runtime will then figure out which server postback event has to be raised for the postback control by looking at its implementation of IPostBackEventHandler.

Triggering Periodic Partial Updates

ASP.NET pages that require frequent updates can be built to expose clickable elements so that users can order a refresh when they feel it is convenient. What if the update occurs frequently and periodically—that is, every n milliseconds? In this case, you can’t ask users to stay there and click all the time. Timers exist to help with such situations. And timers have been incorporated in virtually every browser’s DOM since the beginning of the Web.

The setTimeout method of the DOM window object allows you to create a client timer. Once installed, the timer periodically executes a piece of JavaScript code. In turn, this script code can do whatever is needed—for example, it can start an asynchronous call to the server and update the page automatically.

You can use script-based timers with any version of ASP.NET. To do so, though, you need to have some JavaScript skills and be aware of the characteristics of the browser’s DOM. ASP.NET AJAX shields you from most of the snags with its Timer control.

Generalities of the Timer Control

The Timer control derives from the Control class and implements the IPostBackEventHandler and IScriptControl interface:

public class TimerControl : Control,
             IPostBackEventHandler, IScriptControl

Implemented as a server-side control, it actually creates a client-side timer that performs a postback at regular intervals. When the postback occurs, the Timer server control raises a server-side event named Tick:

public event EventHandler Tick

The control features only two properties, as described in Table 4-4.

Table 4-4. Properties of the Timer Control

Property

Description

Enabled

True by default, the property indicates whether the client-side script needed for the timer is generated when the page is first rendered.

Interval

This integer property indicates the interval at which a client timer raises its Tick event. The interval is expressed in milliseconds and is set by default to 60,000 milliseconds (one minute).

Basically, the Timer control is a server-side interface built around a client timer. It saves you from the burden of creating the timer via script and making it post back when the interval expires.

Using the Timer

The most common use of the Timer control is in conjunction with an UpdatePanel trigger to refresh the panel at regular intervals. The following script defines a timer that posts back every second:

<asp:Timer runat="server" ID="Timer1"
     Interval="1000" OnTick="Timer1_Tick" />

It is extremely important that you carefully consider the impact of a too frequent interval on the overall performance and scalability of your application. Setting the interval to a low value (such as one or two seconds) might cause too many postbacks and traffic on the way to the server. As a result, even asynchronous postbacks performed in partial-rendering mode might incur some undesired overhead.

The following page mixes an UpdatePanel with a Timer control. The timer ticks every second and makes a postback. The UpdatePanel control is bound to the Tick event of the timer using a trigger:

<asp:UpdatePanel runat="server" ID="UpdatePanel1">
    <ContentTemplate>
        <asp:Label runat="server" ID="Label1" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
    </Triggers>
</asp:UpdatePanel>

<asp:Timer runat="server" ID="Timer1"
     Interval="1000" OnTick="Timer1_Tick" />

As a result, the panel is updated every second. Put another way, the following code is run every second on the server resulting in a sort of Web clock:

protected void Timer1_Tick(object sender, EventArgs e)
{
    Label1.Text = DateTime.Now.ToLongTimeString();
}

Figure 4-10 shows the sample page in action.

An ASP.NET AJAX clock in action in a sample page

Figure 4-10. An ASP.NET AJAX clock in action in a sample page

Providing User Feedback During Partial Updates

Updating a panel might not be seamless from a user perspective, however. Having the computer engaged in a potentially long task, in fact, might be problematic. Will the user resist the temptation of reclicking that button over and over again? Will the user patiently wait for the results to show up? Finally, will the user be frustrated and annoyed by waiting without any clue of what’s going on? After all, if the page is sustaining a full postback, the browser itself normally provides some user feedback that this is happening. Using ASP.NET AJAX, the callback doesn’t force a regular full postback and the browser’s visual feedback system is not called upon to inform the user things are happening.

Keep in mind that ASP.NET AJAX Extensions is the ASP.NET incarnation of the AJAX paradigm. And in AJAX, the “A” stands for “asynchronous.” This implies that ASP.NET AJAX developers should take into careful account ways to explain latency to users and, wherever possible, provide ways for users to cancel pending requests.

Because of the need to keep users informed, ASP.NET AJAX supplies the UpdateProgress control to display a templated content while any of the panels in the page are being refreshed.

Generalities of the UpdateProgress Control

The UpdateProgress control is designed to provide any sort of feedback on the browser while one or more UpdatePanel controls are being updated. The UpdateProgress control derives from the Control class and implements the IScriptControl interface—an ASP.NET AJAX-specific interface that qualifies a custom server control as an AJAX control.

public class UpdateProgress : Control, IScriptControl

If you have multiple panels in the page, you might want to find a convenient location in the page for the progress control or, if possible, move it programmatically to the right place with respect to the panel being updated. You can use cascading style sheets (CSSs) to style and position the control at your leisure.

The UpdateProgress control features the properties listed in Table 4-5.

Table 4-5. Properties of the UpdateProgress Control

Property

Description

AssociatedUpdatePanelID

Gets and sets the ID of the UpdatePanel control that this control is associated with.

DisplayAfter

Gets and sets the time in milliseconds after which the progress template is displayed. This property is set to 500 by default.

DynamicLayout

Indicates whether the progress template is dynamically rendered in the page. This property is set to true by default.

ProgressTemplate

Indicates the template displayed during an AJAX postback that is taking longer than the time specified through the DisplayAfter property.

An UpdateProgress control can be bound to a particular UpdatePanel control. You set the binding through the AssociatedUpdatePanelID string property. If no updatable panel is specified, the progress control is displayed for any panels in the page. The user interface of the progress bar is inserted in the host page when the page is rendered. However, it is initially hidden from view using the CSS display attribute.

When set to none, the CSS display attribute doesn’t display a given HTML element and it reuses its space in the page so that other elements can be shifted up properly. When the value of the display attribute is toggled on, existing elements are moved to make room for the new element. If you want to reserve the space for the progress control and leave it blank when no update operation is taking place, you just set the DynamicLayout property to false.

Composing the Progress Screen

ASP.NET AJAX displays the contents of the ProgressTemplate property while waiting for a panel to update. You can specify the template either declaratively or programmatically. In the latter case, you assign the property any object that implements the ITemplate interface. For the former situation, you can easily specify the progress control’s markup declaratively, as shown in the following code:

<asp:UpdateProgress runat="server" ID="UpdateProgress1">
    <ProgressTemplate>
        ...
    </ProgressTemplate>
</asp:UpdateProgress>

You can place any combination of controls in the progress template. However, most of the time you’ll probably just put some text there and an animated GIF. The lefthand page view shown in Figure 4-11 shows the ProgressPanel in action (at the top of the page). Notice the content begins with the letter ‘A’ while the letter ‘F’ is to be queried. The righthand view shows the outcome of the postback, which is to say values beginning with the letter ‘F’ are displayed after the data is asynchronously returned from the server.

A progress template informing users that some work is occurring on the server

Figure 4-11. A progress template informing users that some work is occurring on the server

Note that the UpdateProgress control is not designed to be a gauge component, but rather a user-defined panel that the ScriptManager control shows before the panel refresh begins and that it hides immediately after its completion.

Note

Note

You can dynamically customize the look and feel of the progress control to some extent. All you have to do is write a handler for the Load event of the UpdateProgress control and retrieve the controls in the template using the FindControl method. In this way, you can change some standard text and make it a bit more context sensitive. Likewise, you can change animated images to make the displayed one better reflect the current context. This is often easier than replacing the template with an entirely new ITemplate-based value.

Aborting a Pending Update

A really user-friendly system will always let its users cancel a pending operation. How can you obtain this functionality with an UpdateProgress control? The progress template is allowed to contain an abort button. The script code injected into the page will monitor the button, and it will stop the ongoing asynchronous call if it’s clicked. To specify an abort button, you add the following to the progress template:

<input type="button" onclick="abortTask()" value="Cancel" />

In the first place, the button has to be a client-side button. So you can express it either through the <input> element or the <button> element for browsers that support this element. If you opt for the <input> element, the type attribute must be set to button. The script code you wire up to the onclick event is up to you, but it will contain at least the following instructions:

<script type="text/javascript" >
function abortTask() {
    var obj = Sys.WebForms.PageRequestManager.getInstance();
    if (obj.get_isInAsyncPostBack())
        obj.abortPostBack();
}
</script>

You retrieve the instance of the client PageRequestManager object active in the client page and check whether an AJAX postback is ongoing. If a postback is in progress, you call the abortPostBack method to stop it.

Important

Important

Canceling an ongoing update in this way is equivalent to closing the connection with the server. No results will ever be received and no updates will ever occur on the page. However, canceling the update is a pure client operation and has no effect on what’s happening on the server. If the user started a destructive operation, simply clicking the client-side Cancel button won’t undo the destructive operation on the server.

Associating Progress Screens with Panels

The AssociatedUpdatePanelID property allows you to associate a progress screen with a particular UpdatePanel control so that when the panel is refreshed the screen is displayed to the user. However, the implementation of this property in the context of the UpdateProgress control is not free of issues in spite of the strong sense of simplicity and clearness that name and description suggest.

The property works seamlessly as long as the refresh of an UpdatePanel control is caused by a child control. Should the refresh be ordered by an external trigger, no progress screen would ever be displayed for any panels in the page. This weird behavior is due to the code in the Sys.UI._UpdateProgress JavaScript class—the client object model of the control. The class is defined in the MicrosoftAjaxWebForms.js script file.

Before delving deeper into the reasons for the behavior, let me first address some workarounds. If the page can contain just one UpdateProgress to serve any number of updatable panels, then you’re just fine. You avoid setting the AssociatedUpdatePanelID property for the control and all panels automatically share the same progress screen.

If distinct UpdatePanel controls require distinct progress screens, and these panels are bound to external triggers, the only way for you to display the correct progress screen passes through the addition of a bit of JavaScript code that manually displays the right screen. In other words, you bypass the automatic display mechanism of the UpdateProgress control that fails if an external trigger fires. I’ll show this in a moment after introducing client-side events.

What does cause the script of the UpdateProgress control to fail when an external trigger fires an update? The progress screen is displayed just before the request is sent out and only if the request regards the updatable panel referenced by the AssociatedUpdatePanelID property. The point is, the ID of the panel being updated is not known to the UpdateProgress script. The script attempts to find it out by walking up the hierarchy of the element that caused the postback. Clearly, if the posting element is outside of the UpdatePanel’s tree (i.e., an external trigger) the search is unsuccessful and no progress screen is ever displayed.

Client-Side Events for a Partial Update

Each ASP.NET AJAX postback involves the PageRequestManager client object, which is responsible for invoking, under the hood, the XMLHttpRequest object. What kind of control do developers have on the underlying operation? If you manage XMLHttpRequest directly, you have full control over the request and response. But when these key steps are managed for you, there’s not much you can do unless the request manager supports an eventing model.

The PageRequestManager object provides a few events so that you can customize handling of the request and response.

Events of the Client PageRequestManager Object

The client events listed in Table 4-6 are available on the client PageRequestManager object. As you can see, these events signal the main steps taken when an AJAX postback partially updates a page. The events are listed in the order in which they fire to the client page.

Table 4-6. Events of PageRequestManager Object

Event

Event Argument

Description

initializeRequest

InitializeRequestEventArgs

Occurs before the AJAX request is prepared for sending.

beginRequest

BeginRequestEventArgs

Occurs before the request is sent.

pageLoading

PageLoadingEventArgs

Occurs when the response has been acquired but before any content on the page is updated.

pageLoaded

PageLoadedEventArgs

Occurs after all content on the page is refreshed as a result of an asynchronous postback.

endRequest

EndRequestEventArgs

Occurs after an asynchronous postback is finished and control has been returned to the browser.

To register an event handler, you use the following JavaScript code:

var manager = Sys.WebForms.PageRequestManager.getInstance();
manager.add_beginRequest(OnBeginRequest);

The prototype of the event handler method—OnBeginRequest in this case—is shown here:

function beginRequest(sender, args)

The real type of the args object, though, depends on the event data structure. The other events have similar function prototypes.

Kick In before the Request Starts

The initializeRequest event is the first event in the client life cycle of an AJAX request. The life cycle begins at the moment in which a postback is made that is captured by the UpdatePanel’s client infrastructure. You can use the initializeRequest event to evaluate the postback source and do any additional required work. The event data structure is the InitializeRequestEventArgs class. The class features three properties: postBackElement, request, and cancel.

The postBackElement property is read-only and evaluates to a DomElement object. It indicates the DOM element that is responsible for the postback. The request property (read-only) is an object of type Sys.Net.WebRequest and represents the ongoing request. Finally, cancel is a read-write Boolean property that can be used to abort the request before it is sent.

Immediately after calling the initializeRequest handler, if any, the PageRequestManager object aborts any pending asynchronous requests. Next, it proceeds with the beginRequest event and then sends the packet.

A typical scenario for many Web applications is that the user clicks to start a potentially lengthy operation, no (or not enough) feedback is displayed, and the user keeps on clicking over and over again. Given the implementation of PageRequestManager, any new request kills the current request that is still active. Note that the abort has no effect on the server-side operation; rather, it simply closes the connection and returns an empty response. This potentially results in multiple actions on the server, one for each button click, yet the user will only see the results of the final button click (if they’re patient enough).

By handling the initializeRequest event, though, you can assign a higher priority to the current event and abort any successive requests until the other has terminated. Let’s see how to implement this sort of click-only-once functionality:

<script type="text/javascript">
function pageLoad()
{
   var manager = Sys.WebForms.PageRequestManager.getInstance();
   manager.add_initializeRequest(OnInitializeRequest);
}
function OnInitializeRequest(sender, args)
{
    var manager = Sys.WebForms.PageRequestManager.getInstance();

    // Check if we're posting back because of Button1
    if (manager.get_isInAsyncPostBack() &&
        args.get_postBackElement().id.toLowerCase() == "button1")
    {
        $get("Label1").innerHTML = "Still working on previous request.
                                    Please, be patient ...";
        args.set_cancel(true);
    }
}
</script>

The preceding script aborts any requests originated by the Button1 element if another request from the same element is still being processed. In this way, when the user clicks several times on the same button, no other requests will be accepted as long as there’s one being processed. (See Figure 4-12.)

Users are allowed to start only one high-priority task at a time

Figure 4-12. Users are allowed to start only one high-priority task at a time

Disabling Visual Elements during Updates

If you want to prevent users from generating more input while a partial page update is being processed, you can also consider disabling the user interface—all or in part. To do so, you write handlers for beginRequest and endRequest events:

<script type="text/javascript">
function pageLoad()
{
   var manager = Sys.WebForms.PageRequestManager.getInstance();
   manager.add_beginRequest(OnBeginRequest);
   manager.add_endRequest(OnEndRequest);
}
</script>

The beginRequest event is raised before the processing of an asynchronous postback starts and the postback is sent to the server. You typically use this event to call custom script to animate the user interface and notify the user that the postback is being processed. You can also use the beginRequest event to set a custom request header that identifies your request uniquely.

// Globals
var currentPostBackElem;
var oldStyleString = "";

function OnBeginRequest(sender, args)
{
    currentPostBackElem = args.get_postBackElement();
    if (typeof(currentPostBackElem) === "undefined")
        return;
    if (currentPostBackElem.id.toLowerCase() == "button1")
    {
        // Disable the Load button
        $get("Button1").disabled = true;

        // Optionally, highlight the grid
        oldStyleString = $get("GridView1").style.border;
        $get("GridView1").style.border = "solid red 5px";
    }
}

The beginRequest handler receives event data through the BeginRequestEventArgs data structure—the args formal parameter. The class features only two properties: request and postBackElement. The properties have the same characteristics as the analogous properties on the aforementioned InitializeRequestEventArgs class.

In the preceding code snippet, I disable the clicked button to prevent users from repeatedly clicking the same button. In addition, I draw a thick border around the grid to call the user’s attention to the portion of the user interface being updated. (See Figure 4-13.)

The Load button, disabled and grid-framed during the server processing

Figure 4-13. The Load button, disabled and grid-framed during the server processing

At the end of the request, any temporary modification to the user interface must be removed. So animations must be stopped, altered styles must be restored, and disabled controls re-enabled. The ideal place for all these operations is the endRequest event. The event passes an EndRequestEventArgs object to handlers. The class has a few properties, as described in Table 4-7.

Table 4-7. Properties of the EndRequestEventArgs Class

Property

Description

dataItems

Returns the client-side dictionary packed with server-defined data items for the page or the control that handles this event. (I’ll say more about registering data items later.)

error

Returns an object of type Error that describes the error (if any) that occurred on the server during the request.

errorHandled

Gets and sets a Boolean value that indicates whether the error has been completely handled by user code. If this property is set to true in the event handler, no default error handling will be executed by the Microsoft AJAX client library. We saw an example of this property in Chapter 3.

response

Returns an object of type Sys.Net.WebRequestExecutor that represents the executor of the current request. Most of the time, this object will be an instance of Sys.Net.XMLHttpExecutor. For more information, refer to Chapter 2.

As you can see, when the endRequest event occurs there’s no information available about the client element that fired the postback. If you need to restore some user-interface settings from inside the endRequest event handler, you might need a global variable to track which element caused the postback. In the following code, you need to track the postback trigger as well as the original style of the grid that is shown in Figure 4-13:

function OnEndRequest(sender, args)
{
    if (typeof(currentPostBackElem) === "undefined")
        return;
    if (currentPostBackElem.id.toLowerCase() == "button1")
    {
        $get("Button1").disabled = false;
        $get("GridView1").style.border = oldStyleString;
    }
}

Managing Progress Screens

To display the progress screen, you wait for the beginRequest event, then apply your own logic to decide which screen is appropriate and go. Here’s a quick example:

<script type="text/javascript">
function pageLoad()
{
    var manager = Sys.WebForms.PageRequestManager.getInstance();
    manager.add_beginRequest(OnBeginRequest);
}
function OnBeginRequest(sender, args)
{
   var postBackElement = args.get_postBackElement();
   if (postBackElement.id == 'ButtonTrigger')
     $get('UpdateProgress2').style.display = "block";
}
</script>

You first check the ID of the postback element. Next, based on that information you figure out the update progress block to turn on. You display or hide a block of markup by acting on its display CSS attribute. Additional progress screens would be handled in the same way (i.e., by adding code to display them in OnBeginRequest).

Page Loading Events

In an asynchronous postback, there are two distinct and nested phases: begin/end of the request, and begin/end of the partial page update. After the request has been sent to the server, the client waits for any response to become available.

Note

Note

If you’re curious about the real sequence of steps accomplished to execute an asynchronous AJAX request, take a look at the _onFormSubmit method on the Sys.WebForms.PageRequestManager class. The class is defined in the MicrosoftAjaxWebForms.js script file.

When the response arrives, the PageRequestManager object first processes any returned data and separates hidden fields, updatable panels, data items, and whatever pieces of information are returned from the server. Once the response data is ready for processing, the object fires the pageLoading client event. The event is raised after the server response is received but before any content on the page is updated. You can use this event to provide a custom transition effect for updated content or to run any clean-up code that prepares the panels for the next update.

The event data is packed in an instance of the class PageLoadingEventArgs. The class has three properties: panelsUpdating, panelsDeleting, and dataItems. The first two are arrays and list the updatable panels to be updated and deleted, respectively. The dataItems property is the same as described in Table 4-7. From within a pageLoading event handler, you can’t cancel the page update. Immediately after the pageLoading event, the page request manager starts its rendering engine and updates all involved panels.

The pageLoaded event is raised after all content on the page is refreshed. You can use this event to provide a custom transition effect for updated content, such as flashing or highlighting updated contents. The event data is packed in the class PageLoadedEventArgs, which has three properties: panelsUpdated, panelsDeleted, and dataItems. The first two are arrays and list the updatable panels that were just updated and deleted, respectively. The dataItems property is the same as described in Table 4-7.

You can use this event as well as endRequest to provide notification to users or to log errors.

Passing Data Items during Partial Updates

The UpdatePanel control allows you to wrap groups of controls that need to be updated over a postback. There might be pages, though, in which grouping all controls involved in an operation inside a single panel is too challenging or impractical. What if, therefore, you need to update controls outside the UpdatePanel that fired the call? If the control lives inside another UpdatePanel, you can programmatically order a refresh of the whole panel. What if, instead, just one control needs update? And what if the update on the client must be done with data generated during the asynchronous postback? The RegisterDataItem method of the ScriptManager control addresses exactly this issue.

The RegisterDataItem method specifies a server-generated string that will be added to the response of the asynchronous postback along with the updated markup of the panel or panels. This string is then passed to the client infrastructure of ASP.NET AJAX through the dataItems property of the event data for pageLoading, pageLoaded, and endRequest events.

The RegisterDataItem is used when the page, or a server control, needs to pass additional data to the client that requires explicit processing on the client that is beyond the capabilities of updatable panels. Let’s arrange an example that illustrates the usefulness of data items.

Motivation for Using Data Items

Imagine a page that incorporates a kind of clock. It is made by a timer control that updates a label every second. The panel also contains a couple of buttons to increase and decrease the clock interval.

<asp:UpdatePanel runat="server" ID="UpdatePanel1">
    <ContentTemplate>
        <asp:Label runat="server" ID="Label1" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
        <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
        <asp:AsyncPostBackTrigger ControlID="Button2" EventName="Click" />
    </Triggers>
</asp:UpdatePanel>
<asp:Timer ID="Timer1" runat="server" OnTick="Timer1_Tick"
     Interval="1000" />
<hr />
<asp:Button runat="server" ID="Button1" Text="Increase Interval"
     OnClick="Button1_Click" />
<asp:Button runat="server" ID="Button2" Text="Decrease Interval"
     OnClick="Button2_Click" />

As you can see, the timer is not part of the updatable panel. As such, no markup for the Timer control will be sent over during an AJAX postback. In light of this, what about the following code?

protected void Button1_Click(object sender, EventArgs e)
{
   Timer1.Interval += 1000;
}

This code is certainly executed during the postback. There’s no visible mechanism, though, that ensures that the new interval is passed to the client. Without this key information, how can the client timer update its interval and tick the server properly? However, such code works just fine. You click the button and the clock is updated every two seconds. What’s up with that?

The Timer control internally registers a data item and, through it, passes its server state to the client also over an AJAX postback. Here’s a brief excerpt from the source code of the Timer control:

protected override void OnPreRender(EventArgs e)
{
   base.OnPreRender(e);
   if (ScriptManager.IsInAsyncPostBack)
   {
      ScriptManager.RegisterDataItem(this, GetJsonState(), true);
   }
   ...
}
protected string GetJsonState()
{
   ...
}

The internal GetJsonState function returns a JSON string that renders out as a key/value dictionary object. In particular, the Timer control saves the value of the Enabled and Interval properties—the only two properties that can affect the behavior of the timer on the client.

Preparing Data Items

Let’s see how to proceed to make an ASP.NET AJAX page pass a message for a client label that can’t be included directly in an updatable panel. The data to pass is related to events that occur on the server. For example, imagine you want to display the current interval of the timer and the increment/decrement it underwent on the server. Here’s the code-behind class for page DataItems.aspx:

public partial class Samples_Ch04_Advanced_DataItems : System.Web.UI.Page
{
    private const int OneSecond = 1000;
    private int oldInterval;
    private static bool isDirty = false;

    protected void Page_Load(object sender, EventArgs e) {
        oldInterval = Timer1.Interval;
    }
    protected void Timer1_Tick(object sender, EventArgs e) {
        Label1.Text = DateTime.Now.ToLongTimeString();
    }
    protected void Button1_Click(object sender, EventArgs e) {
        Timer1.Interval += OneSecond;
        isDirty = true;
    }
    protected void Button2_Click(object sender, EventArgs e) {
        if (Timer1.Interval > OneSecond)
        {
            Timer1.Interval -= OneSecond;
            isDirty = true;
        }
    }
    private string GetJsonState() {
        return ("[" + Timer1.Interval.ToString() + "," +
                (Timer1.Interval - oldInterval).ToString() + "]");
    }
    private void Page_PreRender(object sender, EventArgs e) {
        if (isDirty && ScriptManager1.IsInAsyncPostBack)
        {
            isDirty = false;
            ScriptManager1.RegisterDataItem(this, GetJsonState(), true);
        }
    }
}

The AJAX-sensitive state of the page is tracked down and, if dirty, saved as a JSON string during the PreRender event through a call to RegisterDataItem. The state to send to the client is a comma-separated JSON value string that includes the Interval of the timer as modified by Increase and Decrease buttons in the page. RegisterDataItem takes up to three parameters: the object for which the data is registered (page or control), the string to pass, and true if the string is a JSON string.

Note

Note

You get an exception if you call RegisterDataItem in a non-AJAX postback. Tracking the dirtiness of the object’s state is not mandatory but helps in two ways: it reduces the bandwidth and makes it easier for you to manage data items on the client. In fact, the client will receive data only if there’s some data to consume and some user interface to update.

Data Items as JSON Strings

You use the RegisterDataItem method to send data from the server to the client during asynchronous postbacks, regardless of whether the control receiving the data is inside an UpdatePanel control. The data you send is a string and is associated with a particular server control or the page itself. The internal format of the string is up to you. It can be a single value string or a string that represents multiple values—for example, a comma-separated list of values. Needless to say, if you opt for a custom format inside the string, any deserialization is up to you and must be accomplished in the event handler where you get to process sent data items.

Data items are available with the pageLoading event and the following pageLoaded and endRequest events. All event data structures for these events, in fact, feature a dataItems property. The dataItems property is a key/value dictionary where the key is the ID of the control that references the data or __Page if the referrer is the ASP.NET page. The value is the string you passed to RegisterDataItem on the server for that control or page.

When you need to pass multiple values to the client, you can use a JSON-serialized array of values. In JavaScript, an array can be expressed as a comma-separated list of values wrapped by square brackets, as shown in the following code:

var x = [1,2,3];

Returned as a string, the expression must be pre-processed by the eval function to become a valid array:

var x = eval("[1,2,3]");

This is exactly what happens in the code-behind class described earlier, where the GetJsonState method returns an array of strings:

private string GetJsonState()
{
     return ("[" + Timer1.Interval.ToString() + "," +
            (Timer1.Interval-oldInterval).ToString() + "]");
}

On the client, the dataItems object for the page points to an array with interval and last change information.

Note

Note

When data items are expressed in a format that requires a call to eval for them to be transformed into usable client objects, you must turn on the JSON flag on the RegisterDataItem method. When the data item is JSON-enabled, the eval JavaScript function is called to evaluate the returned string. You should only use JSON serialization if you’re passing multiple data values to the client. To learn more about JSON, visit http://json.org/.

Retrieving Data Items on the Client

The dictionary with data items is sent to the client and, as mentioned earlier, is available in the pageLoading, pageLoaded, and endRequest events through the dataItems property on the event argument data object. For a sample page that contains a timer and is bound to the code-behind class considered earlier, the following is an excerpt from the postback response:

11|dataItemJson|__Page|[3000,1000]|
11|dataItemJson|Timer1|[true,3000]|

This text is appended to the response along with the updated markup and new values for hidden fields. The text should be read as follows. It contains two JSON-serialized data items, which is to say they both require eval to evaluate the returned 11 bytes of data each. The first data item is bound to the page and contains an array of two values: 3000 and 1000. The second data item refers to the Timer1 control in the page and contains an array of two values: true and 3000. Let’s see how to retrieve and consume this information programmatically:

function pageLoad()
{
   var manager = Sys.WebForms.PageRequestManager.getInstance();
   manager.add_endRequest(OnEndRequest);
}
function OnEndRequest(sender, args)
{
   var dataItem = args.get_dataItems()["__Page"];
   if (dataItem)
   {
      var text = String.format("Interval set to {0}ms;
                    last change {1}ms.", dataItem[0], dataItem[1]);
      $get("Msg").innerHTML = text;
   }
}

The endRequest handler retrieves the data item record for the page (or any control you’re interested in) and then uses the returned object as an array. In particular, it refreshes the innermost HTML of a label in the page. (See Figure 4-14.)

Refreshing some text in the page that is outside any UpdatePanel controls

Figure 4-14. Refreshing some text in the page that is outside any UpdatePanel controls

The __Page ID used in the preceding example indicates that data items are related to the page. The string is emitted as a generic ID for the page and depends on the object you specify as the first argument of RegisterDataItem. You can also associate data items to a specific control in the page; in that case, the ID to use in the endRequest client-side event handler is the ID of the referenced server control.

Animating Panels during Partial Updates

Most users of the first ASP.NET AJAX applications reported that for them it was a problem not being able to find a visual clue of the changes in the various portions of the page. An UpdatePanel control allows you to partially refresh a page; the operation, though, might pass unnoticed to users, especially when it turns out to be particularly quick or when it serves only a slightly different markup. Animating panels to call a user’s attention to the changes has therefore become the next challenge for ASP.NET AJAX developers.

Visual Feedback during the Partial Update

A primary and quite effective form of animation consists of wiring up the beginRequest and endRequest events of the page request manager and changing the style of controls in the page for the time it takes to complete the server operation. In Figure 4-13 (shown earlier), you see a button that is disabled during the partial update. At the same time, the grid that will receive the new data is framed. Of course, you can choose any combination of style settings to reflect your idea of visual feedback.

For example, in the beginRequest event handler you can disable all controls involved with a postback, change their background color, modify borders, collapse tables, and so on. In the endRequest or pageLoaded handler, you then restore the original settings. As a result, the style of the user interface is altered for the duration of the server request and then restored when the new markup is available.

Visual Feedback after the Page Refresh

Another option is to call the user’s attention only when the modified page is up and running. In this case, nothing happens while the request is processed on the server; as soon as the pageLoaded event is fired, though, the animation starts to notify the user of changes. As a first step, let’s arrange a JavaScript class that implements the animation:

Type.registerNamespace('IntroAjax'),

IntroAjax.BorderAnimation = function IntroAjax$BorderAnimation(
      color, thickness, duration)
{
    this._color = color;
    this._thickness = thickness;
    this._duration = duration;
}

// Method to start the animation on the specified panel
function IntroAjax$BorderAnimation$animatePanel(panelElement)
{
    if (arguments.length !== 1) throw Error.parameterCount();

    var style = panelElement.style;
    style.borderWidth = this._thickness;
    style.borderColor = this._color;
    style.borderStyle = 'solid';

    window.setTimeout( function() {{style.borderWidth = 0;}},
                       this._duration);
}

IntroAjax.BorderAnimation.prototype = {
    animatePanel: IntroAjax$BorderAnimation$animatePanel
}

IntroAjax.BorderAnimation.registerClass('IntroAjax.BorderAnimation'),

The BorderAnimation class features an animatePanel method that renders out a solid border of the specified thickness, color, and duration around all the panels updated during the postback.

For the script to be executed, you must first register the .js file with script manager:

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="animation.js" />
    </Scripts>
</asp:ScriptManager>

Then you need to add some script to the page to trigger it:

<script type="text/javascript">
var postbackElement;
var animation;

function pageLoad() {
    var manager = Sys.WebForms.PageRequestManager.getInstance();
    manager.add_beginRequest(OnBeginRequest);
    manager.add_pageLoaded(OnPageLoaded);
}

function OnBeginRequest(sender, args) {
    postbackElement = args.get_postBackElement();
}

function OnPageLoaded(sender, args) {
    animation = new IntroAjax.BorderAnimation('red', '5px', 2000);

    var updatedPanels = args.get_panelsUpdated();
    if (typeof(postbackElement) === "undefined")
        return;
    else if (postbackElement.id.toLowerCase().indexOf('button1') > -1)
    {
        for (i=0; i < updatedPanels.length; i++)
            animation.animatePanel(updatedPanels[i]);
    }
}
</script>

As the page loads up, you register handlers for the beginRequest and pageLoaded events. The beginRequest handler doesn’t do anything related to the user interface; it simply tracks the DOM element that caused the asynchronous postback. The handler for the pageLoaded event creates an instance of the animation class and uses it to animate all panels that have been updated. It does that only if the ID of the postback element matches the ID stored in the postbackElement element variable, indicating it is the same element that triggered the postback (in this case “button1”). In this way, you can animate based on the specific action commanded. (See Figure 4-15.)

Updated panels are framed for a few seconds to get the user’s attention

Figure 4-15. Updated panels are framed for a few seconds to get the user’s attention

Note

Note

The definitive solution for animating updatable panels is not in the ASP.NET AJAX Extensions platform but just outside of it, in the AJAX Control Toolkit. (See http://ajax.asp.net/ajaxtoolkit.)

As we’ll see in Chapter 5,”The AJAX Control Toolkit”, the AJAX Control Toolkit is a joint project between the developer community and Microsoft. It extends the ASP.NET AJAX Extensions platform with a collection of Web client components, including controls and control extenders. One of coolest extenders is the UpdatePanelAnimation component. Attached to an UpdatePanel, the component automatically injects client script to animate the panel during the postback operation.

Conclusion

The UpdatePanel control and other similar server controls provide an excellent compromise between the need to implement asynchronous and out-of-band functionality and the desire to use the same familiar ASP.NET application model. As you’ve seen in this chapter, any ASP.NET page can be easily transformed into an ASP.NET AJAX page. You divide the original page into regions and assign each markup region to a distinct UpdatePanel control. From that point on, each updatable region can be refreshed individually through independent and asynchronous calls that do not affect the rest of the page. The current page remains up and active while regions are updated. This feature is known as partial rendering.

If required, you can define one or more triggers for each updatable region so that the update of each region occurs under specified and well-known conditions. The UpdatePanel and other server controls covered in this chapter represent the first, and certainly the easiest, path to using ASP.NET AJAX. With minimal effort, you can transform an ordinary ASP.NET page into an AJAX page and start practicing with the new framework until you feel the need to use braveheart JavaScript code.

In the next chapter, we’ll look at another key category of ASP.NET AJAX server controls—the extenders. A control extender is a server-side component that adds a new behavior and extra capabilities to a bunch of existing ASP.NET controls.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.69.49