Chapter 31. Using Server-Side ASP.NET AJAX

<feature>

IN THIS CHAPTER

</feature>

Microsoft ASP.NET is a dying technology. It received its death blow on February 18, 2005 when Jesse James Garrett published his article “Ajax: A New Approach to Web Applications.” All that is left is the long, slow goodbye.

All the previous chapters in this book were about ASP.NET. ASP.NET is the present. This chapter, and the two that follow, are about the future. The future is Ajax. In this chapter and the next, you learn about server-side ASP.NET AJAX. In the chapter that follows, you learn about client-side ASP.NET AJAX.

In this chapter, you learn about Microsoft’s primary server-side AJAX control: the UpdatePanel control. This control enables you to easily retrofit existing ASP.NET applications with Ajax functionality. You use the UpdatePanel control to update content in a page without posting the entire page back to the web server. You also learn about two controls that support the UpdatePanel control: the Timer control and the UpdateProgress control.

Note

You’ll notice in this chapter that I switch between Ajax and AJAX. Jesse James Garrett insists that Ajax should be spelled Ajax (initial-capped), and Microsoft insists that the word should be spelled AJAX (all capped, like a proper acronym). Because Mr. Garrett coined the word, I’ll use his preferred spelling, Ajax, when not referring to a Microsoft product name.

The Ajax Vision

ASP.NET is a server-side technology for building web applications. Almost all the work happens on the web server and not the web browser. Whenever you perform an action in an ASP.NET page—such as clicking a button or sorting a GridView—the entire page must be posted back to the web server. Any significant action on a page results in a postback.

If you think about it, this is incredibly inefficient. When you perform a postback in an ASP.NET page, the entire page must be transported across the Internet from browser to server. Next, the .NET class that corresponds to the page must re-render the entire page again from scratch. Finally, the finished page must be sent back across the Internet to the browser. This whole long, slow, agonizing process must occur even if you are updating a tiny section of the page.

Using a server-side technology such as ASP.NET results in a bad user experience. Every time a user performs some action on a page, the universe temporarily freezes. Whenever you perform a postback, the browser locks, the page jumps, and the user must wait patiently twiddling his thumbs while the page gets reconstructed. All of us have grown accustomed to this awful user experience. However, we would never design our desktop applications in the same way.

When the members of the ASP.NET team invented ASP.NET in the late 1990s, there was good reason to embrace the server side. Getting a page that was written in JavaScript to work consistently across different browsers, and even across different versions of the same browser, was very difficult. The server side was safe and reliable.

However, we’ve reached a tipping point. Web developers are discovering that if they want to build truly great applications, they need to leave the safety of the server side and enter the wilds of the client side. Google has hacked out a path for us by creating several proof-of-concept web applications that demonstrate that you can build reliable, user-friendly, web applications that execute entirely in the browser without the need for postbacks.

Google Docs (http://docs.google.com) demonstrates that you can build Microsoft Office better than Office by building it as a web application. Google Docs enables you to save your documents, spreadsheets, and presentations on a central server so that they don’t get lost and can be accessed anywhere. Furthermore, Google Docs enables people to collaborate on documents and spreadsheets over the Internet, which is something that you just cannot do in Microsoft Office.

Google Suggest (http://www.google.com/webhp?complete=1&hl=en) was the Google application that convinced me that the future is Ajax. Google Suggest works like the normal Google home page, except for the fact that the Google Suggest page offers suggestions as you type. While you are typing, Google Suggest looks up matching words from its database and shows them to you in real time (before seeing Google Suggest, I would have thought this violated the laws of physics).

Finally, Google Gmail (http://gmail.google.com) is the application that started all the excitement about Ajax. Gmail is an email client that works very much like a desktop email application. Jesse James Garrett credits Google Gmail for inspiring him to coin the word Ajax.

Note

It is ironic that Google Gmail, an email web client, is the application that got everyone excited about Ajax because the technology behind Ajax was invented by Microsoft in support of its email web client. Microsoft invented AJAX to support its web client for Microsoft Exchange Server. You can read about the invention of the technology behind Ajax in the following blog post:

http://msexchangeteam.com/archive/2005/06/21/406646.aspx

An Ajax application is a client-side web application written using native browser technologies such JavaScript and the DOM. A pure Ajax application is a web application that consists of a single page and performs all its communications with the web server through web service calls.

Note

Applications that use client-side technologies such as Flash, Flex, Java applets, and Silverlight don’t count as Ajax applications because these are proprietary technologies. An Ajax application must use native browser technologies.

Unlike a server-side web application, an Ajax application can be very responsive to user interaction. If a user clicks a button in a server-side web application, the button Click event doesn’t actually happen until the page gets posted back to the server. In a server-side application, the button Click event gets shifted in time and space. In a client-side Ajax application, on the other hand, the button Click event happens when it happens: right on the browser.

In an Ajax application, the user interface layer is located in the browser (where it should be). The business logic and data access layers are located on the server. The user interface layer accesses the business logic layer through web services.

In this section, I’ve tried to convey to you the Ajax vision. I’ve tried to explain why I am excited about Ajax and why I think most web applications will be written as Ajax applications in the future. Unfortunately, however, the ideal does not completely intersect with the real, and we have not quite reached the point where we can build pure Ajax applications. But we are very close.

Note

To learn more about Ajax, I recommend that you visit the Ajaxian.com website and go to the Ajaxian conferences.

Server-Side Ajax versus Client-Side Ajax

Microsoft has a complicated relationship with Ajax. On the one hand, the company wants to provide its existing ASP.NET developers with an easy way to implement Ajax functionality without having to learn JavaScript. On the other hand, Microsoft recognizes that the future is on the client. Therefore, it wants to provide web developers with the tools they need to build pure client-side Ajax applications. For these reasons, Microsoft has both a server-side Ajax framework and a client-side Ajax framework.

If you want to retrofit an existing ASP.NET application to take advantage of Ajax, you can take advantage of Microsoft’s server-side Ajax framework. To take advantage of the server-side framework, you don’t need to write a single line of JavaScript code. You can continue to build ASP.NET pages with server-side controls in the standard way. You learn how to take advantage of the server-side Ajax framework in this chapter.

The advantage of the server-side framework is that it provides existing ASP.NET developers with a painless method of doing Ajax. The disadvantage of the server-side framework is that it doesn’t escape all the problems associated with a server-side framework. You still have to run back to the server whenever you perform any client-side action.

The Microsoft client-side Ajax framework (which we discuss in Chapter 33, “Using Client-Side ASP.NET AJAX”) embraces the client side. When building applications with the Microsoft client-side Ajax framework, you must build the application by using JavaScript. The advantage of building applications with the client-side framework is that you can build very rich and responsive web applications. You can build web applications with the same rich interactivity as a desktop application. The disadvantage is that Microsoft’s client-side framework is currently not fully baked.

Debugging Ajax Applications

Before we start discussing the Microsoft AJAX frameworks, you need to be aware of two crucial debugging tools. Debugging Ajax applications presents challenges not present in a normal server-side application. If an Ajax call fails, you won’t necessarily know. You need a way of monitoring the Ajax calls that happen between the browser and server.

The first tool is called Fiddler. You can download this tool (for free) at http://www.fiddler2.com. Fiddler enables you to view HTTP requests and responses, including Ajax calls. Fiddler works by installing itself as a proxy between your web browser and the rest of the universe. You can use Fiddler with Internet Explorer, Mozilla Firefox, Opera, Safari, and just about any other browser.

After you install Fiddler, you can launch the tool by selecting the menu option Tools, Fiddler2 from within Microsoft Internet Explorer. After Fiddler launches, every browser request and response is recorded in the Fiddler Web Sessions pane. You can click a request and then click the Session Inspector tab to see the full request and response (see Figure 31.1).

Using Fiddler to inspect an Ajax request and response.

Figure 31.1. Using Fiddler to inspect an Ajax request and response.

Note

If you can’t get Fiddler to capture page requests from localhost, try adding a period directly after localhost in the browser address bar. For example, make a request that looks like this:

http://localhost.:6916/Original/Feedback.aspx

If you are using Microsoft Vista, you might need to disable IPv6 support. In Fiddler, select the menu option Tools, Fiddler Options, and uncheck the Enable IPv6 check box.

The other critical Ajax debugging tool is Firebug, which is a free Firefox extension. You can download Firebug by launching Firefox and selecting the menu option Tools, Add-ons. Next, click the Get Extensions link. Finally, enter Firebug into the search box and follow the installation instructions.

Firebug, like Fiddler, enables you to monitor Ajax calls, but it enables you to do much more. After you install Firebug, you enable it by selecting the menu option Tools, Firebug and unchecking Disable Firebug. After Firebug is enabled, you can click the green check box at the bottom right of the Firefox browser to open Firebug (see Figure 31.2).

Using Firebug in Mozilla Firefox.

Figure 31.2. Using Firebug in Mozilla Firefox.

Firebug has several very useful features for debugging JavaScript applications. For example, it enables you to set breakpoints in JavaScript scripts, inspect DOM elements, and determine which CSS rules apply to which elements in a page. Right now, however, I want you to notice that you can use Firebug to monitor Ajax requests and responses. If you click the Net tab and the XHR tab, then every Ajax call will appear in the Firebug window. You can click a particular Ajax request to see the full request and response interaction between browser and server.

Using the UpdatePanel Control

Microsoft’s server-side AJAX framework consists of one main control: the UpdatePanel control. The UpdatePanel control enables you to update a portion of a page without updating the entire page. In other words, it enables you to perform partial-page rendering.

Let’s start with a super-simple example of a page that uses the UpdatePanel control. The page in Listing 31.1 contains a ScriptManager control and an UpdatePanel control. The UpdatePanel control contains a single Button control. When you click the button, only the content contained in the UpdatePanel control is refreshed (see Figure 31.3).

Using the UpdatePanel control.

Figure 31.3. Using the UpdatePanel control.

Example 31.1. UpdatePanelSimple.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>UpdatePanel Simple</title>
</head>
<body>
    <form id="form1" runat="server">

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

    Page Time: <%= DateTime.Now.ToString("T") %>
    <br /><br />

    <asp:UpdatePanel
        id="up1"
        runat="server">
        <ContentTemplate>
        UpdatePanel Time: <%= DateTime.Now.ToString("T") %>
        <br />
        <asp:Button
            id="btn"
            Text="Update"
            runat="server" />
        </ContentTemplate>
    </asp:UpdatePanel>

    </form>
</body>
</html>

The page in Listing 31.1 displays the current time both inside and outside the UpdatePanel control. When you click the button, only the time within the UpdatePanel control is refreshed.

Let’s look at a more realistic example that just begs for some Ajax (see Figure 31.4). The page in Listing 31.2 does not use any of the ASP.NET AJAX controls. It contains two cascading DropDownList controls. The first DropDownList enables you to pick a state, and the second DropDownList enables you to pick a city. The list of cities changes depending on the state selected.

A page with cascading DropDownList controls.

Figure 31.4. A page with cascading DropDownList controls.

Example 31.2. CascadingDropDownsNoAjax.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Cascading DropDownList Controls</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblState"
        Text="State:"
        AssociatedControlID="ddlState"
        Runat="server" />
    <asp:DropDownList
        id="ddlState"
        DataSourceID="srcState"
        DataTextField="State"
        DataValueField="State"
        AutoPostBack="true"
        Runat="server" />
    <asp:SqlDataSource
        id="srcState"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT State FROM State
            ORDER BY State"
        Runat="server" />

    <br /><br />


    <asp:Label
        id="Label1"
        Text="City:"
        AssociatedControlID="ddlCity"
        Runat="server" />
    <asp:DropDownList
        id="ddlCity"
        DataSourceID="srcCity"
        DataTextField="City"
        AutoPostBack="true"
        Runat="server" />
    <asp:SqlDataSource
        id="srcCity"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT City FROM City
            WHERE State=@State
            ORDER BY City"
        Runat="server">
        <SelectParameters>
            <asp:ControlParameter Name="State" ControlID="ddlState" />
        </SelectParameters>
    </asp:SqlDataSource>

    </div>
    </form>
</body>
</html>

When you select a state using the first DropDownList control, there is a click, and the page posts back to itself in order to populate the second DropDownList control with matching cities. Clearly, the user experience here is less than optimal. All work must stop while the page performs a postback.

Let’s fix up this page with some Ajax. The page in Listing 31.3 is exactly the same as the page in Listing 31.2, except for two changes. First, the page now contains a ScriptManager control. Second, and more importantly, the DropDownList controls in Listing 31.3 are wrapped inside an UpdatePanel control.

Example 31.3. CascadingDropDownsAjax.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Cascading DropDownList Controls</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    <asp:UpdatePanel
        id="UpdatePanel1"

        Runat="server">
        <ContentTemplate>

    <asp:Label
        id="lblState"
        Text="State:"
        AssociatedControlID="ddlState"
        Runat="server" />
    <asp:DropDownList
        id="ddlState"
        DataSourceID="srcState"
        DataTextField="State"
        DataValueField="State"
        AutoPostBack="true"
        Runat="server" />
    <asp:SqlDataSource
        id="srcState"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT State FROM State
            ORDER BY State"
        Runat="server" />

    <br /><br />


    <asp:Label
        id="Label1"
        Text="City:"
        AssociatedControlID="ddlCity"
        Runat="server" />
    <asp:DropDownList
        id="ddlCity"
        DataSourceID="srcCity"
        DataTextField="City"
        AutoPostBack="true"
        Runat="server" />
    <asp:SqlDataSource
        id="srcCity"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT City FROM City
            WHERE State=@State
            ORDER BY City"
        Runat="server">
        <SelectParameters>
            <asp:ControlParameter Name="State" ControlID="ddlState" />
        </SelectParameters>
    </asp:SqlDataSource>

    </ContentTemplate>
    </asp:UpdatePanel>

    </div>
    </form>
</body>
</html>

In Listing 31.3, when you select a new state with the first DropDownList control, matching cities are displayed in the second DropDownList control. However, there is no click and there is no noticeable postback. The browser doesn’t freeze, and the page does not jump. Everything happens smoothly and professionally through the magic of Ajax.

The ScriptManager control in Listing 31.3 is used to add the necessary JavaScript scripts to enable Ajax. Anytime you create a page that uses Ajax, regardless of whether you are doing server-side or client-side Ajax, you’ll add a ScriptManager control to the page.

Note

If you select the AJAX Web Form template instead of the normal Web Form template from the Website, Add New Item menu option, then you get the ScriptManager control automatically.

Another option is to pick the AJAX Master Page template, which also includes the ScriptManager control. If you use the AJAX Master Page, you don’t need to add the ScriptManager control to individual content pages.

The UpdatePanel control is the control that is doing all the Ajax work here. It hijacks the normal postback that would happen when you select a new item in the first DropDownList control. The UpdatePanel hijacks the normal postback and performs a “sneaky” postback to grab the new content in the background.

Let’s look at another page that takes advantage of the UpdatePanel control. The page in Listing 31.4 represents a simple customer feedback form (see Figure 31.5). The page contains a FormView control and a GridView control. The FormView control is used to render the insert form, and the GridView control is used to display previous customer responses. You can sort the contents of the GridView in order of the different columns.

Entering customer feedback into an Ajax-enabled form.

Figure 31.5. Entering customer feedback into an Ajax-enabled form.

Example 31.4. Feedback.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Feedback</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />


    <asp:UpdatePanel
        id="up1"
        Runat="server">
        <ContentTemplate>

    <asp:FormView
        id="frmFeedback"
        DataSourceId="srcFeedback"
        DefaultMode="Insert"
        Runat="server">
        <InsertItemTemplate>

        <asp:Label
            id="lblName"
            Text="Name:"
            AssociatedControlID="txtName"
            Runat="server" />
        <asp:RequiredFieldValidator
            id="valName"
            Text="Required"
            ControlToValidate="txtName"
            Runat="server" />
        <br />
        <asp:TextBox
            id="txtName"
            Text='<%# Bind("Name") %>'
            Runat="server" />
        <br /><br />
        <asp:Label
            id="lblComment"
            Text="Comment:"
            AssociatedControlID="txtComment"
            Runat="server" />
        <asp:RequiredFieldValidator
            id="valComment"
            Text="Required"
            ControlToValidate="txtComment"
            Runat="server" />
        <br />
        <asp:TextBox
            id="txtComment"
            Text='<%# Bind("Comment") %>'
            TextMode="MultiLine"
            Columns="50"
            Rows="3"
            Runat="server" />
        <br /><br />
        <asp:Button
            id="btnSubmit"
            Text="Submit"
            CommandName="Insert"
            Runat="server" />
        </InsertItemTemplate>
    </asp:FormView>
    <br /><br />

    <asp:GridView
        id="grdFeedback"
        DataSourceID="srcFeedback"
        AllowSorting="true"
        Runat="server" />

    </ContentTemplate>
    </asp:UpdatePanel>


    <asp:SqlDataSource
        id="srcFeedback"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT Id,Name,Comment,DateSubmitted
            FROM Feedback"
        InsertCommand="INSERT Feedback (Name,Comment)
            VALUES (@Name,@Comment)"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Because the UpdatePanel control in Listing 31.4 contains both the FormView and GridView, you can interact with the page without performing a single postback. When you submit the form, the form data is submitted back to the server using Ajax. When you sort the columns in the GridView, the sorted rows are retrieved from the server through an Ajax call.

The UpdatePanel control has six important properties:

  • ChildrenAsTriggersGets or sets a Boolean value that indicates whether child controls should trigger an asynchronous postback automatically.

  • ContentTemplateContainerGets the container for the UpdatePanel control’s ContentTemplate. You can add controls to the ContentTemplate programmatically using this property.

  • IsInPartialRenderingGets a Boolean value indicating whether the UpdatePanel is being rendered in response to an asynchronous postback.

  • RenderModeGets or sets a value that indicates whether the contents of an UpdatePanel should be enclosed in an HTML <div> or <span> tag. Possible values are Block (the default) and Inline.

  • TriggersGets a list of controls that trigger the UpdatePanel to perform either an asynchronous or synchronous postback.

  • UpdateModeGets or sets a value indicating when the content of the UpdatePanel is updated. Possible values are Always (the default) and Conditional.

The UpdatePanel also supports the following single important method:

  • Update()Causes the UpdatePanel to update its contents.

You learn how to take advantage of these properties and methods in the following sections.

Specifying UpdatePanel Triggers

By default, an UpdatePanel hijacks any postbacks that any of its child controls performs. For example, if a Button control is contained in an UpdatePanel, the UpdatePanel will hijack the button Click event and perform an Ajax call instead of the normal postback.

You can cause an UpdatePanel to refresh its contents from a control located outside of the UpdatePanel by specifying a trigger. For example, the page in Listing 31.5 contains a Button control outside of an UpdatePanel that causes the UpdatePanel to refresh its content.

Example 31.5. TriggerUpdatePanel.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Trigger Update Panel</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    Page Time: <%= DateTime.Now.ToString("T") %>
    <br />
    <asp:Button
        id="btnUpdate"
        Text="Update"
        Runat="server" />

    <asp:UpdatePanel
        id="up1"
        Runat="server">
        <Triggers>
            <asp:AsyncPostBackTrigger
                ControlID="btnUpdate"
                EventName="Click" />
        </Triggers>
        <ContentTemplate>

        Update Panel Time: <%= DateTime.Now.ToString("T") %>

        </ContentTemplate>
    </asp:UpdatePanel>

    </div>
    </form>
</body>
</html>

The UpdatePanel in Listing 31.5 includes a Triggers sub-element that contains a single AsyncPostBackTrigger. This trigger points to the Button control located outside of the UpdatePanel named btnUpdate. Because the UpdatePanel contains this trigger, clicking the Button control causes the UpdatePanel to refresh its contents.

If you want, you can prevent the UpdatePanel from refreshing its contents unless you have explicitly created a trigger. If you set the UpdatePanel control’s ChildrenAsTriggers property to the value false, you must explicitly create a trigger to update the contents of the UpdatePanel.

The UpdatePanel supports two types of triggers: AsyncPostBackTrigger and PostBackTrigger. The AsyncPostBackTrigger causes an asynchronous (Ajax) postback. The PostBackTrigger causes a normal entire-page postback.

You’ll rarely use a PostBackTrigger. The only situation in which it makes sense to use a PostBackTrigger is when you need to mix buttons that cause asynchronous postbacks and normal postbacks in the same UpdatePanel control. For example, because you cannot perform a file upload without performing a normal entire-page postback, if a file-upload button is contained in an UpdatePanel, you need to create a PostBackTrigger for the file-upload button.

Nesting UpdatePanel Controls

One UpdatePanel can contain another UpdatePanel. In fact, you can nest UpdatePanels to your heart’s content, just like Russian nesting dolls.

Nesting UpdatePanel controls is useful when you want to control how much of a page gets refreshed during an asynchronous postback. Sometimes, you might need to update only a tiny portion of a page, and other times you might need to update the entire page.

For example, the page in Listing 31.6 contains two nested UpdatePanels. The outer UpdatePanel contains a DropDownList, FormView, and ListView control. The inner UpdatePanel contains only the ListView control (see Figure 31.6).

Page with nested UpdatePanel controls.

Figure 31.6. Page with nested UpdatePanel controls.

Example 31.6. NestedUpdatePanels.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Nested UpdatePanels</title>
    <style type="text/css">
        fieldset
        {
            padding: 10px;
        }
        .comment
        {
            padding: 10px;
            border: dotted 2px black;
            margin: 10px;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    Page Time: <%= DateTime.Now.ToString("T") %>
    <br />

    <asp:DropDownList
        id="ddlMovie"
        DataSourceID="srcMovies"
        DataValueField="Id"
        DataTextField="Title"
        AutoPostBack="true"
        Runat="server" />
    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT Id, Title FROM Movie"
        Runat="server" />

    <br /><br />

    <asp:UpdatePanel ID="upOuter" UpdateMode="Conditional" runat="server">
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="ddlMovie" />
    </Triggers>
    <ContentTemplate>

    Outer UpdatePanel Time: <%= DateTime.Now.ToString("T") %>
    <br />

    <asp:FormView
        id="frmMovie"
        DataSourceID="srcMovie"
        Runat="server">
        <ItemTemplate>
        <fieldset>
    <legend>Movie</legend>
    Title: <%# Eval("Title") %>
    <br />
    Director: <%# Eval("Director") %>

    <asp:UpdatePanel ID="upInner" runat="server">
    <ContentTemplate>
    <asp:ListView
        id="lstMovieComments"
        DataSourceID="srcMovieComments"
        InsertItemPosition="FirstItem"
        Runat="server">
        <LayoutTemplate>
            <fieldset>
            <legend>Comments</legend>
            Inner UpdatePanel Time: <%= DateTime.Now.ToString("T") %>
            <div id="itemContainer" runat="server">
            </div>
        </fieldset>
        </LayoutTemplate>
        <ItemTemplate>
            <div class="comment">
            <%# Eval("Comment") %>
            </div>
        </ItemTemplate>
        <InsertItemTemplate>
        <asp:Label
            id="lblComment"
            Text="Comment:"
            AssociatedControlID="txtComment"
            Runat="server" />
        <br />
        <asp:TextBox
            id="txtComment"
            Text='<%# Bind("Comment") %>'
            TextMode="MultiLine"
            Columns="40"
            Rows="3"
            Runat="server" />
        <br />
        <asp:Button
            id="btnInsert"
            Text="Add Comment"
            CommandName="Insert"
            Runat="server" />
        </InsertItemTemplate>
        </asp:ListView>
        </ContentTemplate>
        </asp:UpdatePanel>
        <asp:SqlDataSource
            id="srcMovieComments"
            ConnectionString='<%$ ConnectionStrings:con %>'
            SelectCommand="SELECT Id, Comment
                FROM MovieComment
                WHERE MovieId=@MovieId"
            InsertCommand="INSERT MovieComment (Comment,MovieId)
                VALUES (@Comment,@MovieId)"
            Runat="server">
            <SelectParameters>
                <asp:ControlParameter Name="MovieId" ControlID="ddlMovie" />
            </SelectParameters>
            <InsertParameters>
                <asp:ControlParameter Name="MovieId" ControlID="ddlMovie" />
            </InsertParameters>
        </asp:SqlDataSource>
        </fieldset>
        </ItemTemplate>
    </asp:FormView>
    </ContentTemplate>
    </asp:UpdatePanel>
    <asp:SqlDataSource
        id="srcMovie"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT Id, Title, Director
            FROM Movie
            WHERE Id=@Id"
        Runat="server">
        <SelectParameters>
            <asp:ControlParameter Name="Id" ControlID="ddlMovie" />
        </SelectParameters>
    </asp:SqlDataSource>

    </div>
    </form>
</body>
</html>

When you select a movie by using the DropDownList control, the entire page is updated. When you add a new comment to the movie with the ListView control, on the other hand, only the comments portion of the page is updated.

There are two UpdatePanel controls. The first UpdatePanel control has an ID of upOuter. It includes a trigger that points to the DropDownList control used to select a movie. Notice that this UpdatePanel control has its UpdateMode property set to the value Conditional. If the UpdateMode property was not set to this value, the outer UpdatePanel would refresh its content when the Add Comment button contained in the inner UpdatePanel control was clicked.

The inner UpdatePanel is named upInner. This UpdatePanel surrounds the ListView used to display the form for adding and displaying movie comments. When you add a new movie comment, only the comments area of the page is updated and not the entire page.

The page, the outer UpdatePanel, and the inner UpdatePanel all display the current time. When you select a new movie, the time displayed by both the outer and inner UpdatePanel—but not the page—changes. When you add a new comment, only the time displayed by the inner UpdatePanel changes.

In general, for performance reasons, you should place the smallest possible area that you need to update inside of an UpdatePanel control. The larger the area contained in an UpdatePanel, the more content that must be passed across the Internet when the UpdatePanel is updated. By nesting UpdatePanel controls, you have more granular control over the content that gets updated in a page.

Updating UpdatePanels Programmatically

The UpdatePanel control includes an Update() method. You can use this method to update the content of an UpdatePanel programmatically during an asynchronous postback.

Two properties determine when an UpdatePanel control updates its contents: UpdateMode and ChildrenAsTriggers. If you set the UpdateMode property to the value Conditional and you set ChildrenAsTriggers to the value false (and you don’t define any triggers), the only way to update an UpdatePanel control’s content is by calling the Update() method.

For example, the page in Listing 31.7 enables you to search movies by title. The page contains two UpdatePanel controls. The first UpdatePanel control contains a TextBox control and a Button control. The second UpdatePanel control contains a GridView control. When you click the button, the Button Click event is raised on the server through an asynchronous postback. The second UpdatePanel that contains the GridView of results is updated if, and only if, any results are found that match the search query.

Example 31.7. UpdateUpdatePanel.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    protected void btnSearch_Click(object sender, EventArgs e)
    {
        ArrayList results = Movie.Search(txtSearch.Text);
        if (results.Count > 0)
        {
            grdResults.DataSource = results;
            grdResults.DataBind();
            upResults.Update();
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Update UpdatePanel</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    <asp:UpdatePanel
        id="upSearch"
        Runat="server">
        <ContentTemplate>
        <asp:TextBox
            id="txtSearch"
            Runat="server" />
        <asp:Button
            id="btnSearch"
            Text="Search"
            OnClick="btnSearch_Click"
            Runat="server" />
        </ContentTemplate>
    </asp:UpdatePanel>

    <asp:UpdatePanel
        id="upResults"
        UpdateMode="Conditional"
        Runat="server">
        <ContentTemplate>
        Results Time: <%= DateTime.Now.ToString("T") %>
        <br />
        <asp:GridView
            id="grdResults"
            runat="server" />
        </ContentTemplate>
    </asp:UpdatePanel>

    </div>
    </form>
</body>
</html>

UpdatePanels and JavaScript

You must take special care when using JavaScript with UpdatePanel controls. If you use the standard methods of the ClientScriptManager class for working with JavaScript, they will fail when called during an asynchronous request.

For example, I often use the Page.ClientScript.RegisterStartupScript() method from my server-side code to inject a JavaScript script into a page dynamically. The page in Listing 31.8 contains a button labeled Delete All Files. When you click the button, and the FileHelper.DeleteAll() method returns true, then a JavaScript alert box displays the message “All Files Deleted Successfully!” (see Figure 31.7).

Displaying a JavaScript alert.

Figure 31.7. Displaying a JavaScript alert.

Example 31.8. ShowAlert.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnDeleteAll_Click(object sender, EventArgs e)
    {
        if (FileHelper.DeleteAll() == true)
        {
            string script = @"alert('All Files Deleted Successfully!'),";
            Page.ClientScript.RegisterStartupScript(this.GetType(), "filesDeleted", script, true);
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show Alert</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Button
        id="btnDeleteAll"
        Text="Delete All Files"
        OnClick="btnDeleteAll_Click"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Unfortunately, the page in Listing 31.8 does not work when the Button control is wrapped in an UpdatePanel. The JavaScript alert never appears after you click the button. The page fails silently.

If you need to inject JavaScript into a page when performing an asynchronous postback, you need to take advantage of the methods exposed by the ScriptManager class. The ScriptManager class duplicates all the standard JavaScript methods of the ClientScriptManager class, including the following:

  • RegisterArrayDeclaration()Enables you to add a JavaScript array to the page.

  • RegisterClientScriptBlock()Enables you to add an inline JavaScript script right after the opening <form> tag.

  • RegisterClientScriptInclude()Enables you to add a JavaScript <script src=""> tag to a page.

  • RegisterClientScriptResource()Enables you to add a reference to a JavaScript file embedded in an assembly.

  • RegisterExpandoAttribute()Enables you to register a tag expando.

  • RegisterOnSubmitStatement()Enables you to register a JavaScript script that is executed when the form is submitted.

  • RegisterStartupScript()Enables you to add an inline JavaScript script right before the closing <form> tag.

The page in Listing 31.9 demonstrates how you can add JavaScript from the server to a page when performing an asynchronous postback.

Example 31.9. ShowAlertUpdatePanel.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnDeleteAll_Click(object sender, EventArgs e)
    {
        if (FileHelper.DeleteAll() == true)
        {
            string script = @"alert('All Files Deleted Successfully!'),";
            ScriptManager.RegisterStartupScript(this, this.GetType(), "filesDeleted", script, true);
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Show Alert UpdatePanel</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    <asp:UpdatePanel id="up1" runat="server">
    <ContentTemplate>
    UpdatePanel Time: <%= DateTime.Now.ToString("T") %>
    <br />
    <asp:Button
        id="btnDeleteAll"
        Text="Delete All Files"
        OnClick="btnDeleteAll_Click"
        Runat="server" />

    </ContentTemplate>
    </asp:UpdatePanel>

    </div>
    </form>
</body>
</html>

In Listing 31.9, the Button control is wrapped in an UpdatePanel. When you click the button, the ScriptManager.RegisterStartupScript() method is used to add the JavaScript alert to the page dynamically.

UpdatePanel Server-Side Page Execution Lifecycle

It is important to understand that a server-side page goes through its normal page execution lifecycle when you perform an asynchronous postback. The Page PreInit, Init, Load, and PreRender events are raised for an asynchronous postback in just the same way as these events are raised for a normal postback.

The page in Listing 31.8 logs each server event and displays the log in a BulletedList control (see Figure 31.8).

Viewing an asynchronous postback’s server lifecycle.

Figure 31.8. Viewing an asynchronous postback’s server lifecycle.

Example 31.10. ServerLifecycle.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    public ArrayList _log = new ArrayList();

    void Page_PreInit()
    {
        _log.Add("PreInit " + sm1.IsInAsyncPostBack);
    }

    void Page_Init()
    {
        _log.Add("Init " + sm1.IsInAsyncPostBack);
    }

    void Page_Load()
    {
        _log.Add("Load " + sm1.IsInAsyncPostBack);
    }

    void Page_PreRender()
    {
        _log.Add("PreRender " + sm1.IsInAsyncPostBack);

        // Show Lifecycle log
        bltLog.DataSource = _log;
        bltLog.DataBind();
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Server Lifecycle</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        runat="server" />

    <asp:UpdatePanel
        id="up1"
        runat="server">
        <ContentTemplate>
        <asp:Button
            id="btnLog"
            Text="Show Server Page Lifecycle"
            Runat="server" />
        <asp:BulletedList
            id="bltLog"
            Runat="server" />

        </ContentTemplate>
    </asp:UpdatePanel>

    </div>
    </form>
</body>
</html>

When you first open the page in Listing 31.10, each page event is listed in the BulletedList control. Next to each event, you’ll see the word False. The ScriptManager.IsInAsyncPostBack property is used to display whether the page is being processed within a normal postback or an asynchronous postback.

The page includes an UpdatePanel that contains a Button control. Clicking the button initiates an asynchronous postback. After you click the button, the exact same list of events appears in the BulletedList control. The exact same events are raised during an asynchronous postback as are raised during a normal postback.

Note

Notice that ScriptManager.IsInAsyncPostBack has the value False when the PreInit event is raised during an asynchronous postback. This IsInAsyncPostBack property is updated after this event. (So it is just wrong.)

UpdatePanel Client-Side Page Execution Lifecycle

A page that contains a ScriptManager control not only has a server-side page execution lifecycle, it also has a client-side page execution lifecycle. The following series of events happen on the client-side:

  • Application.initRaised when a page is first requested. This event is not raised during an asynchronous postback.

  • PageRequestManager.initializeRequestRaised before an asynchronous request to the server starts.

  • PageRequestManager.beginRequestRaised before an asynchronous request to the server starts.

  • PageRequestManager.pageLoadingRaised after an asynchronous response is received from the server but before UpdatePanel content is updated.

  • PageRequestManager.pageLoadedRaised after an asynchronous response is received from the server and after UpdatePanel content is updated. Also raised during the initial page request.

  • Application.loadRaised during both normal and asynchronous postbacks.

  • PageRequestManager.endRequestRaised after an asynchronous response both when there is and when there isn’t an error.

  • Application.unloadRaised before the user leaves or reloads the page.

Two client-side objects raise client lifecycle events: the Sys.Application object and the Sys.WebForms.PageRequestManager object. The Sys.Application events happen in a page regardless of whether or not the page contains UpdatePanel controls. The Sys.WebForms.PageRequestManager events are tied to UpdatePanels.

The page in Listing 31.11 illustrates when each of these client-side events occurs. The page takes advantage of ASP.NET AJAX’s client-side trace support. When each client event occurs, Sys.Debug.trace() is used to write a message to the Trace Console. Figure 31.9 shows the page after the Async Postback button is clicked.

Viewing an asynchronous page’s client execution lifecycle.

Figure 31.9. Viewing an asynchronous page’s client execution lifecycle.

Example 31.11. ClientLifecycle.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Client Lifecycle</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />

        <asp:UpdatePanel ID="up1" runat="server">
        <ContentTemplate>
            <asp:Button ID="btnAsync" Text="Async Postback" runat="server" />
        </ContentTemplate>
        </asp:UpdatePanel>
        <asp:Button ID="Button1" Text="Normal Postback" runat="server" />

        <br /><br />
        <textarea id="TraceConsole" cols="60" rows="10"></textarea>

    </div>
    </form>
</body>
<script type="text/javascript">

  Sys.Application.add_init(application_init);

  function application_init()
  {
    Sys.Debug.trace("Application.Init");

    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_initializeRequest( prm_initializeRequest );
    prm.add_beginRequest( prm_beginRequest );
    prm.add_pageLoading( prm_pageLoading );
    prm.add_pageLoaded( prm_pageLoaded );
    prm.add_endRequest( prm_endRequest );
  }

  function pageLoad()
  {
    Sys.Debug.trace("Application.Load");
  }

  function prm_initializeRequest()
  {
    Sys.Debug.trace("PageRequestManager.initializeRequest");
  }

  function prm_beginRequest()
  {
    Sys.Debug.trace("PageRequestManager.beginRequest");
  }

  function prm_pageLoading()
  {
    Sys.Debug.trace("PageRequestManager.pageLoading");
  }

  function prm_pageLoaded()
  {
    Sys.Debug.trace("PageRequestManager.pageLoaded");
  }

  function prm_endRequest()
  {
    Sys.Debug.trace("PageRequestManager.endRequest");
  }

  function pageUnload()
  {
    alert("Application.Unload");
  }

</script>
</html>

Notice that because we are discussing client-side events, we have moved over into the JavaScript world. The script in Listing 31.11 has to be written in JavaScript because it executes within the browser and not on the server.

Note

When using Sys.Debug.trace() with Internet Explorer, you must add a <textarea> to your page with an ID of TraceConsole to view the trace messages. When using Mozilla Firefox with the Firebug extension, on the other hand, you can simply use the Firebug console to view the trace messages.

Different information is available during each client-side event. You can access the event information by reading the properties of the second parameter passed to the event handler. What follows is the event information passed to each event handler.

InitializeRequestEventArgs

Passed to the PageRequestManager.initializeRequest event handler. Supports the following properties:

  • cancelEnables you to cancel the current asynchronous postback

  • postBackElementThe element that caused the asynchronous postback

  • requestThe request object used to perform the asynchronous postback

BeginRequestEventArgs

Passed to the PageRequestManager.beginRequest event handler. Supports the following properties:

  • postBackElementThe element that caused the asynchronous postback

  • requestThe request object used to perform the asynchronous postback

PageLoadingEventArgs

Passed to the PageRequestManager.pageLoading event handler. Supports the following properties:

  • dataItemsThe data items registered with the ScriptManager.RegisterDataItem() method

  • panelsDeletingThe array of UpdatePanel elements being deleted

  • panelsUpdatingThe array of UpdatePanel elements being updated

PageLoadedEventArgs

Passed to the PageRequestManager.pageLoaded event handler. Supports the following properties:

  • dataItemsThe data items registered with the ScriptManager.RegisterDataItem() method

  • panelsCreatedThe array of UpdatePanel elements created

  • panelsUpdatedThe array of UpdatePanel elements updated

ApplicationLoadEventArgs

Passed to the Application.load event handler. Supports the following properties:

  • componentsThe array of components created since the last time the Application.load event was raised

  • isPartialLoadIndicates whether the page is executing in the context of an asynchronous postback

EndRequestEventArgs

Passed to the PageRequestManager.endRequest event handler. Supports the following properties:

  • dataItemsThe data items registered with the ScriptManager.RegisterDataItem() method

  • errorThe error, if any, that occurred during the asynchronous postback

  • errorHandledEnables you to suppress the error

  • responseThe response associated with the asynchronous postback

Note

You can detect whether a page is executing within the context on an asynchronous postback within client code by using the PageRequestManager.isInAsyncPostBack property.

The page in Listing 31.12 illustrates how you can take advantage of these event properties. The page contains two UpdatePanel controls. During an asynchronous call, the border of the active UpdatePanel turns the color orange. When the asynchronous call completes, the border of the updated UpdatePanel turns green.

Note

Later in this chapter, you learn how to use the UpdateProgress control to display an UpdatePanel’s progress. The method described in this section of handling client events directly is useful when you want to display a custom progress indicator.

Example 31.12. UpdatePanelCustomProgress.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(2000); // sleep 2 seconds
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>UpdatePanelCustomProgress</title>
    <style type="text/css">
        .normal
        {
            width:300px;
            padding:10px;
            margin:10px;
            border: solid 4px black;
        }

        .updating
        {
            width:300px;
            padding:10px;
            margin:10px;
            border: solid 4px orange;
        }

        .updated
        {
            width:300px;
            padding:10px;
            margin:10px;
            border: solid 4px green;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">

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

    <div id="panelContainer">
    <asp:UpdatePanel id="up1" UpdateMode="Conditional" runat="server">
    <ContentTemplate>
        <%= DateTime.Now.ToString("T") %>
        <asp:Button
            id="btnSubmit1"
            Text="Submit 1"
            OnClick="btnSubmit_Click"
            Runat="server" />
    </ContentTemplate>
    </asp:UpdatePanel>

    <asp:UpdatePanel id="up2" UpdateMode="Conditional" runat="server">
    <ContentTemplate>
        <%= DateTime.Now.ToString("T") %>
        <asp:Button
            id="btnSubmit2"
            Text="Submit 2"
            OnClick="btnSubmit_Click"
            Runat="server" />
    </ContentTemplate>
    </asp:UpdatePanel>
    </div>
    </form>
    <script type="text/javascript">
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_beginRequest(prm_beginRequest);
    prm.add_pageLoaded(prm_pageLoaded);

    function prm_beginRequest(sender, args)
    {
        var container = args.get_postBackElement().parentNode;
        container.className = 'updating';
    }

    function prm_pageLoaded(sender, args)
    {
        var panelsCreated = args.get_panelsCreated();
        for (var k=0;k<panelsCreated.length;k++)
            panelsCreated[k].className = 'normal';

        var panelsUpdated = args.get_panelsUpdated();
        for (var k=0;k<panelsUpdated.length;k++)
            panelsUpdated[k].className = 'updated';
    }

    </script>
</body>
</html>

When the page in Listing 31.12 first loads in your browser, the PageRequestManager pageLoaded event is raised and the prm_pageLoaded event handler executes. This event handler assigns a default CSS class (named normal) to each of the UpdatePanel controls in the page. The list of UpdatePanels is retrieved from the PageLoadedEventArgs.panelsCreated property.

If you click the first button, the border around the first button turns orange until the asynchronous postback completes and the border turns green. The same thing happens when you click the second button.

When you click a button, the PageRequestManager beginRequest event is raised and the border around the button turns orange. After the response is returned from the server, the PageRequestManager pageLoaded event is raised and the border around the button turns green. The list of updated UpdatePanels is retrieved from the PageLoadedEventArgs.updated property.

What happens if you click both buttons in rapid succession? In that case, you are attempting to perform two simultaneous asynchronous postbacks. Unfortunately, the UpdatePanel does not support multiple simultaneous asynchronous postbacks. By default, the last postback performed will abort all previous postbacks.

Note

I was, quite bluntly, flabbergasted when I learned that multiple UpdatePanel controls cannot perform an asynchronous postback at the same time. This is a very disappointing limitation of the Microsoft AJAX Framework.

Canceling the Current Asynchronous Postback

As you learned in the previous section, you can perform at most one asynchronous postback in a page at a time. By default, the last postback wins. If you initiate a new postback while a previous postback is being processed, the previous postback is aborted.

If you want to reverse this logic, and give precedence to the first postback over future postbacks, you can cancel every postback that occurs after the first postback until the first postback completes. The page in Listing 31.13 illustrates how to cancel an asynchronous postback in the event handler for the PageRequestManager.initializeRequest event (see Figure 31.10).

Canceling an asynchronous postback.

Figure 31.10. Canceling an asynchronous postback.

Example 31.13. UpdatePanelCancel.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(3000); // sleep 3 seconds
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>UpdatePanel Cancel</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <asp:UpdatePanel ID="up1" UpdateMode="Conditional" runat="server">
    <ContentTemplate>
        <%= DateTime.Now.ToString("T") %>
        <asp:Button
            id="btnSubmit1"
            Text="Submit 1"
            OnClick="btnSubmit_Click"
            Runat="server"/>
    </ContentTemplate>
    </asp:UpdatePanel>

    <asp:UpdatePanel ID="up2" UpdateMode="Conditional" runat="server">
    <ContentTemplate>
        <%= DateTime.Now.ToString("T") %>
        <asp:Button
            id="btnSubmit2"
            Text="Submit 2"
            OnClick="btnSubmit_Click"
            Runat="server" />
    </ContentTemplate>
    </asp:UpdatePanel>

    </form>
    <script type="text/javascript">

    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_initializeRequest( prm_initializeRequest );

    function prm_initializeRequest(sender, args)
    {
        if (prm.get_isInAsyncPostBack())
        {
            alert('Still Processing First Request'),
            args.set_cancel(true);
        }
    }
    </script>

</body>
</html>

Using similar logic, you can always give precedence to one UpdatePanel over another. Listing 31.14 contains client-script that always gives precedence to the btnSubmit1 button over any other button that causes an asynchronous postback in the page. (The entire page is included on the CD that accompanies this book.)

Example 31.14. UpdatePanelPrecedence.aspx

<script type="text/javascript">


    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_initializeRequest( prm_initializeRequest );

    var prevPostBackElementId;

    function prm_initializeRequest(sender, args)
    {
        if (prm.get_isInAsyncPostBack())
        {
            if (prevPostBackElementId == 'btnSubmit1')
            {
                alert('Still Processing btnSubmit1 Request'),
                args.set_cancel(true);
            }
        }
        prevPostBackElementId = args.get_postBackElement().id;
    }
</script>

If you click the second button (btnSubmit2) immediately after clicking the first button (btnSubmit1), the second asynchronous postback is canceled.

Aborting the Previous Asynchronous Postback

You can explicitly abort a previous asynchronous postback by using the PageRequestManager abortPostBack() method. Explicitly aborting a postback is useful when you want to associate a Cancel button with an asynchronous postback (see Figure 31.11).

Aborting an asynchronous postback with a Cancel button.

Figure 31.11. Aborting an asynchronous postback with a Cancel button.

For example, the page in Listing 31.15 contains two buttons. The first button retrieves your fortune. The oracle, however, is slow. It takes 3 seconds for the oracle to deliver a new fortune. If you want to cancel the new fortune during these 3 seconds, you can click the Cancel button.

Warning

Aborting an asynchronous postback does not actually stop any work that has been started on the server. Aborting simply causes the Page Request Manager to ignore any response returned from the server. If you want to cancel a long-running process on the server, you need to perform another asynchronous call which performs logic that explicitly halts the long-running process.

Example 31.15. UpdatePanelAbort.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnGetFortune_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(3000); // wait 3 seconds
        lblFortune.Text = String.Format("At {0:T}, the oracle says: ", DateTime.Now);
        Random rnd = new Random();
        switch (rnd.Next(4))
        {
            case 0:
                lblFortune.Text += "You're doomed!";
                break;
            case 1:
                lblFortune.Text += "Good luck is around the corner.";
                break;
            case 2:
                lblFortune.Text += "Don't leave home.";
                break;
            case 3:
                lblFortune.Text += "Buy stock today.";
                break;
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>UpdatePanel Abort</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />

        <asp:UpdatePanel ID="up1" runat="server">
        <ContentTemplate>
            <asp:Button
                id="btnGetFortune"
                Text="Get Fortune"
                OnClick="btnGetFortune_Click"
                Runat="server" />
            <asp:Button
                id="btnCancel"
                Text="Cancel"
                Enabled="false"
                Runat="server" />
                <br />
                <asp:Label ID="lblFortune" runat="server" />
        </ContentTemplate>
        </asp:UpdatePanel>

    </div>
    </form>
    <script type="text/javascript">

    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_initializeRequest(prm_initializeRequest);

    function prm_initializeRequest(sender, args)
    {
        if (args.get_postBackElement().id == 'btnCancel')
        {
            prm.abortPostBack();
            alert("Fortune Aborted!");
        }
        else
        {
            $get('btnCancel').disabled = false;
        }
     }
    </script>
</body>
</html>

Passing Additional Information During an Asynchronous Postback

You can pass additional items from the web server to the web browser during an asynchronous postback. Passing additional items is useful when the area that you need to update on a page does not fall into a neat little rectangle. For example, you might want to update a page’s title or a page’s meta tags based on the results of an asynchronous query.

The page in Listing 31.16 contains a DetailsView control that you can use to navigate the contents of the Movie database table. The DetailsView control is contained inside of an UpdatePanel control so that a postback does not happen when you navigate to a new movie.

Example 31.16. UpdatePanelDataItem.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void dtlMovie_DataBound(object sender, EventArgs e)
    {
        string movieTitle = (string)DataBinder.Eval(dtlMovie.DataItem, "Title");

        if (sm1.IsInAsyncPostBack)
        {
            sm1.RegisterDataItem(Head1, movieTitle);
        }
        else
        {
            Head1.Title = movieTitle;
            hTitle.InnerHtml = movieTitle;
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>UpdatePanel DataItem</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    <h1 id="hTitle" runat="server"></h1>


    <asp:UpdatePanel
        id="upSearch"
        Runat="server">
        <ContentTemplate>

        <asp:DetailsView
            id="dtlMovie"
            DataSourceID="srcMovies"
            AllowPaging="true"
            Runat="server" OnDataBound="dtlMovie_DataBound" />

        </ContentTemplate>
    </asp:UpdatePanel>

    <asp:SqlDataSource
        id="srcMovies"
        ConnectionString='<%$ ConnectionStrings:con %>'
        SelectCommand="SELECT Id,Title,Director FROM Movie"
        Runat="server" />

    </div>
    </form>
    <script type="text/javascript">

    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_pageLoaded( prm_pageLoaded );

    function prm_pageLoaded(sender, args)
    {
        if (prm.get_isInAsyncPostBack())
        {
            var movieTitle = args.get_dataItems()['Head1'];
            // assign browser title bar
            document.title = movieTitle;
            // assign heading
            $get('hTitle').innerHTML = movieTitle;
        }
    }
    </script>
</body>
</html>

When you navigate to a new movie, both the browser title bar and the page heading are updated to display the title of the new movie (see Figure 31.12). The title and heading are updated by passing a data item that represents the movie title during the asynchronous postback.

Updating a page’s header and title asynchronously.

Figure 31.12. Updating a page’s header and title asynchronously.

Handling UpdatePanel Errors Gracefully

Sometimes things go terribly wrong. The Internet gets clogged, an application’s database server goes down, and so on. How do you recover from these types of errors gracefully in an Ajax application?

By default, if an error occurs during an asynchronous postback, a JavaScript alert box appears that displays an error message. This is a jarring experience in a production application.

You have several options for avoiding this default experience: You can configure a custom error page, you can handle the error on the server side, or you can handle the error on the client side. Let’s examine each of these options.

First, if you configure a custom error page for your application, then by default the custom error page applies to asynchronous postback errors. You enable a custom error page by adding the following element to the system.web section of your web configuration file:

<customErrors mode="On" defaultRedirect="ErrorPage.aspx" />

This element enables a custom error page for both local and remote requests. Any unhandled exceptions in any page cause the browser to be redirected to a page named ErrorPage.aspx.

The page in Listing 31.17 throws an exception when you click the button located in the UpdatePanel control. If you open the page in Listing 31.15 with a custom error page enabled, the browser is redirected to the ErrorPage.aspx page automatically.

Example 31.17. UpdatePanelError.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        throw new Exception("Server Error");
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>UpdatePanel Error</title>
</head>
<body>
    <form id="form1" runat="server">

    <asp:ScriptManager
        id="sm1"
        Runat="server" />

    <asp:UpdatePanel
        id="up1"
        runat="server">
        <ContentTemplate>

        <asp:Button
            id="btnSubmit"
            Text="Submit"
            OnClick="btnSubmit_Click"
            Runat="server" />

        </ContentTemplate>
    </asp:UpdatePanel>

    </form>
</body>
</html>

You can disable custom error pages in the case of an asynchronous postback by adding an AllowCustomErrorRedirect attribute to the ScriptManager tag, like this:

<asp:ScriptManager
   id="sm1"
   AllowCustomErrorsRedirect="false"
   Runat="server" />

Instead of redirecting the user to an error page, you can customize the error message that the user sees. You can customize the error on both the server and the client.

On the server, you can handle the ScriptManager control’s AsyncPostBackError event to customize the error message transmitted to the client. For example, the page in Listing 31.18 modifies the error message to be a generic one.

Example 31.18. UpdatePanelErrorServer.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        throw new Exception("Server Error");
    }

    protected void sm1_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
    {
        sm1.AsyncPostBackErrorMessage = "A server error occurred";
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>UpdatePanel Error Server</title>
</head>
<body>
    <form id="form1" runat="server">

    <asp:ScriptManager
        id="sm1"
        OnAsyncPostBackError="sm1_AsyncPostBackError"
        Runat="server" />

    <asp:UpdatePanel
        id="up1"
        runat="server">
        <ContentTemplate>

        <asp:Button
            id="btnSubmit"
            Text="Submit"
            OnClick="btnSubmit_Click"
            Runat="server" />

        </ContentTemplate>
    </asp:UpdatePanel>

    </form>
</body>
</html>

The page in Listing 31.18 cloaks the actual server-side error message with a generic message. The error message displayed by the page is still not very professional. Most likely, you’ll want to customize the error message even more when the error is displayed on the client.

The page in Listing 31.19 illustrates how you can customize an error message on the client. The page displays an error message directly above the UpdatePanel when an asynchronous postback fails (see Figure 31.13).

Customizing a client-side error message.

Figure 31.13. Customizing a client-side error message.

Example 31.19. UpdatePanelErrorClient.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        throw new Exception("Server Error");
    }

    protected void sm1_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
    {
        sm1.AsyncPostBackErrorMessage = "A server error occurred";
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>UpdatePanel Error Server</title>
    <style type="text/css">

    .errorMessage
    {
        background-color: Yellow;
        color: Red;
    }

    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager
        id="sm1"
        OnAsyncPostBackError="sm1_AsyncPostBackError"
        Runat="server" />

    <span id="spanError" class="errorMessage"></span>

    <asp:UpdatePanel
        id="up1"
        runat="server">
        <ContentTemplate>

        <asp:Button
            id="btnSubmit"
            Text="Submit"
            OnClick="btnSubmit_Click"
            Runat="server" />

        </ContentTemplate>
    </asp:UpdatePanel>

    </form>
    <script type="text/javascript">

    var prm = Sys.WebForms.PageRequestManager.getInstance();
    prm.add_endRequest( prm_endRequest );

    function prm_endRequest(sender, args)
    {
        var spanError = $get("spanError");
        if (args.get_error())
        {
            args.set_errorHandled(true);
            spanError.innerHTML = "Could not complete your request";
        }
        else
        {
            spanError.innerHTML = "";
        }
    }

    </script>
</body>
</html>

Before leaving this section, I need to mention one last property supported by the ScriptManager control that is related to errors: the AsyncPostBackTimeOut property. This property determines the amount of time in seconds before an asynchronous postback times out. The default value is 90 seconds. You might want to set this value to a briefer duration.

UpdatePanel Performance

The UpdatePanel hides the normal page postback by performing an asynchronous (sneaky) postback. Even though you can use the UpdatePanel to trick your users into believing that a postback is not occurring, it is important that you do not trick yourself.

You can use either of the two debugging tools discussed earlier in this chapter to view the Ajax request and response that occur during an asynchronous postback. For example, Listing 31.20 contains a typical Ajax request, and Listing 31.21 contains a typical Ajax response.

Example 31.20. Ajax Request

sm1=up1%7CgrdFeedback&__EVENTTARGET=grdFeedback&__EVENTARGUMENT=Sort%24Name&__VIEWSTATE=%2FwEPDwUKLTk4MzMyODc2MQ9kFgICAw9kFgICAw9kFgJmD2QWBAIBDzwrAAoBAA8WBB4LXyFEYXRhQm91bmRnHgtfIUl0ZW1Db3VudGZkFgJmD2QWBGYPDxYCHgdWaXNpYmxlaGRkAgIPDxYCHwJoZGQCAw88KwANAgAPFgQfAGcfAQIEZAwUKwAEFggeBE5hbWUFAklkHgpJc1JlYWRPbmx5aB4EVHlwZRkrAR4JRGF0YUZpZWxkBQJJZBYIHwMFBE5hbWUfBGgfBRkrAh8GBQROYW1lFggfAwUHQ29tbWVudB8EaB8FGSsCHwYFB0NvbW1lbnQWCB8DBQ1EYXRlU3VibWl0dGVkHwRoHwUZKVxTeXN0ZW0uRGF0ZVRpbWUsIG1zY29ybGliLCBWZXJzaW9uPTIuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OR8GBQ1EYXRlU3VibWl0dGVkFgJmD2QWCgIBD2QWCGYPDxYCHgRUZXh0BQE0ZGQCAQ8PFgIfBwUFU3RldmVkZAICDw8WAh8HBRJIZXJlIGlzIG15IGNvbW1lbnRkZAIDDw8WAh8HBRQxMC8zLzIwMDcgNDo1MjowNCBQTWRkAgIPZBYIZg8PFgIfBwUBM2RkAgEPDxYCHwcFA0JvYmRkAgIPDxYCHwcFFUhleSwgd2hhdCBhYm91dCBBamF4P2RkAgMPDxYCHwcFFDEwLzMvMjAwNyA0OjE5OjI1IFBNZGQCAw9kFghmDw8WAh8HBQExZGQCAQ8PFgIfBwUFc3RldmVkZAICDw8WAh8HBRVXaGF0IGEgZ3JlYXQgd2Vic2l0ZSFkZAIDDw8WAh8HBRQxMC8zLzIwMDcgNDowOTo1NiBQTWRkAgQPZBYIZg8PFgIfBwUBMmRkAgEPDxYCHwcFBXN0ZXZlZGQCAg8PFgIfBwVaV293LCBpdCBpcyB3cml0dGVuIGVudGlyZWx5IHdpdGggTGlucT8gVGhhdCBtdXN0IGhhdmUgc2F2ZWQgeW91IGEgbG90IG9mIGRldmVsb3BtZW50IHRpbWUhZGQCAw8PFgIfBwUUMTAvMy8yMDA3IDQ6MDk6NTYgUE1kZAIFDw8WAh8CaGRkGAIFC2dyZEZlZWRiYWNrDzwrAAkCBAUHQ29tbWVudAgCAWQFC2ZybUZlZWRiYWNrDxQrAAdkZAICZGQWAGRkuZs7yL%2Fem%2BLQG%2FRqUcYBa9aTsI4%3D&frmFeedback%24txtName=&frmFeedback%24txtComment=&__EVENTVALIDATION=%2FwEWCALS%2BMLfAgKVvojNBgKio6JkAp7t150BAtnw1uUHApCu1%2B4GAoKl7PcLAoGY7eABIj9XtltK55e8Og9%2BNK4DglwM43M%3D&

Example 31.21. Ajax Response

2124|updatePanel|up1|

    <table cellspacing="0" border="0" id="frmFeedback"
      style="border-collapse:collapse;">
    <tr>
        <td colspan="2">
        <label for="frmFeedback_txtName" id="frmFeedback_lblName">Name:</label>
        <span id="frmFeedback_valName" style=
          "color:Red;visibility:hidden;">Required</span>
        <br />
        <input name="frmFeedback$txtName" type="text" id="frmFeedback_txtName" />
        <br /><br />
        <label for="frmFeedback_txtComment" id=
          "frmFeedback_lblComment">Comment:</label>
        <span id="frmFeedback_valComment" style=
          "color:Red;visibility:hidden;">Required</span>
        <br />
        <textarea name="frmFeedback$txtComment" rows="3" cols="50"
          id="frmFeedback_txtComment"></textarea>
        <br /><br />
        <input type="submit" name="frmFeedback$btnSubmit" value="Submit"
          onclick="javascript:WebForm_DoPostBackWithOptions
          (new WebForm_PostBackOptions(&quot;frmFeedback$btnSubmit&quot;,
          &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))"
           id="frmFeedback_btnSubmit" />
        </td>
    </tr>
</table>

    <br /><br />

    <div>
    <table cellspacing="0" rules="all" border="1" id="grdFeedback"
             style="border-collapse:collapse;">
        <tr>
             <th scope="col"><a href="javascript:__
                            doPostBack('grdFeedback','Sort$Id')">Id</a></th>
                            <th scope="col"><a href="javascript:__doPostBack
                            ('grdFeedback','Sort$Name')">Name</a></th><th
                              scope="col">
                            <a href="javascript:__doPostBack
                            ('grdFeedback','Sort$Comment')">Comment</a></th>
                            <th scope="col"><a href="javascript:__doPostBack
                            ('grdFeedback','Sort$DateSubmitted')">
                             DateSubmitted</a></th>
        </tr><tr>
            <td>3</td><td>Bob</td><td>Hey, what about Ajax?
                             </td><td>10/3/2007 4:19:25 PM</td>
        </tr><tr>
            <td>1</td><td>steve</td><td>What a great website!
                            </td><td>10/3/2007 4:09:56 PM</td>
        </tr><tr>
            <td>2</td><td>steve</td><td>Wow, it is written entirely
                            with Linq? That must have saved you a lot of
                            development time!</td><td>10/3/2007 4:09:56 PM</td>
        </tr><tr>
            <td>4</td><td>Steve</td><td>Here is my comment
                           </td><td>10/3/2007 4:52:04 PM</td>
        </tr>
    </table>
</div>

    |0|hiddenField|__EVENTTARGET||0|hiddenField|__EVENTARGUMENT||1264|hiddenField|__VIEWSTATE|/wEPDwUKLTk4MzMyODc2MQ9kFgICAw9kFgICAw9kFgJmD2QWBAIBDzwrAAoBAA8WBB4LXyFEYXRhQm91bmRnHgtfIUl0ZW1Db3VudGZkFgJmD2QWBGYPDxYCHgdWaXNpYmxlaGRkAgIPDxYCHwJoZGQCAw88KwANAgAPFgQfAGcfAQIEZAwUKwAEFggeBE5hbWUFAklkHgpJc1JlYWRPbmx5aB4EVHlwZRkrAR4JRGF0YUZpZWxkBQJJZBYIHwMFBE5hbWUfBGgfBRkrAh8GBQROYW1lFggfAwUHQ29tbWVudB8EaB8FGSsCHwYFB0NvbW1lbnQWCB8DBQ1EYXRlU3VibWl0dGVkHwRoHwUZKVxTeXN0ZW0uRGF0ZVRpbWUsIG1zY29ybGliLCBWZXJzaW9uPTIuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OR8GBQ1EYXRlU3VibWl0dGVkFgJmD2QWCgIBD2QWCGYPDxYCHgRUZXh0BQEzZGQCAQ8PFgIfBwUDQm9iZGQCAg8PFgIfBwUVSGV5LCB3aGF0IGFib3V0IEFqYXg/ZGQCAw8PFgIfBwUUMTAvMy8yMDA3IDQ6MTk6MjUgUE1kZAICD2QWCGYPDxYCHwcFATFkZAIBDw8WAh8HBQVzdGV2ZWRkAgIPDxYCHwcFFVdoYXQgYSBncmVhdCB3ZWJzaXRlIWRkAgMPDxYCHwcFFDEwLzMvMjAwNyA0OjA5OjU2IFBNZGQCAw9kFghmDw8WAh8HBQEyZGQCAQ8PFgIfBwUFc3RldmVkZAICDw8WAh8HBVpXb3csIGl0IGlzIHdyaXR0ZW4gZW50aXJlbHkgd2l0aCBMaW5xPyBUaGF0IG11c3QgaGF2ZSBzYXZlZCB5b3UgYSBsb3Qgb2YgZGV2ZWxvcG1lbnQgdGltZSFkZAIDDw8WAh8HBRQxMC8zLzIwMDcgNDowOTo1NiBQTWRkAgQPZBYIZg8PFgIfBwUBNGRkAgEPDxYCHwcFBVN0ZXZlZGQCAg8PFgIfBwUSSGVyZSBpcyBteSBjb21tZW50ZGQCAw8PFgIfBwUUMTAvMy8yMDA3IDQ6NTI6MDQgUE1kZAIFDw8WAh8CaGRkGAIFC2dyZEZlZWRiYWNrDzwrAAkCBAUETmFtZQgCAWQFC2ZybUZlZWRiYWNrDxQrAAdkZAICZGQWAGRkVKO/p/Z+TKr7wPvuagKWmQ2FfIY=|96|hiddenField|__EVENTVALIDATION|/wEWCAKuyYyNBQKVvojNBgKio6JkAp7t150BAtnw1uUHApCu1+4GAoKl7PcLAoGY7eABqkyic8N4MLIm8nwM1bpWblCsXyA=|0|asyncPostBackControlIDs|||0|postBackControlIDs|||4|updatePanelIDs||tup1|0|childUpdatePanelIDs|||3|panelsToRefreshIDs||up1|2|asyncPostBackTimeout||90|13|formAction||Feedback.aspx|8|pageTitle||Feedback|46|arrayDeclaration|Page_Validators|document.getElementById("frmFeedback_valName")|49|arrayDeclaration|Page_Validators|document.getElementById("frmFeedback_valComment")|139|scriptBlock|ScriptPath|/Original/ScriptResource.axd?d=pGcnA3xf7SUaukdr-behbvslg2hOq48wA9WuXk0fdM20k9xho9i9m9JZzVPbP2-5l3cHqVSeROczjHZXGFjpag2&t=633231592768281250|367|scriptBlock|ScriptContentWithTags|{"text":"
u003c!—
var Page_ValidationActive = false;
if (typeof(ValidatorOnLoad) == "function") {
    ValidatorOnLoad();
}

function ValidatorOnSubmit() {
    if (Page_ValidationActive) {
        return ValidatorCommonOnSubmit();
    }
    else {
 return true;
    }
}
// —u003e
","type":"text/javascript"}|90|onSubmit||if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;|21|expando|document.getElementById('frmFeedback_valName')['controltovalidate']|"frmFeedback_txtName"|39|expando|document.getElementById('frmFeedback_valName')['evaluationfunction']|"RequiredFieldValidatorEvaluateIsValid"|2|expando|document.getElementById('frmFeedback_valName')['initialvalue']|""|24|expando|document.getElementById('frmFeedback_valComment')['controltovalidate']|"frmFeedback_txtComment"|39|expando|document.getElementById('frmFeedback_valComment')['evaluationfunction']|"RequiredFieldValidatorEvaluateIsValid"|2|expando|document.getElementById('frmFeedback_valComment')['initialvalue']|""|78|scriptDispose|up1|Array.remove(Page_Validators, document.getElementById('frmFeedback_valName'));|81|scriptDispose|up1|Array.remove(Page_Validators, document.getElementById('frmFeedback_valComment'));|

The Ajax request and response in Listing 31.20 and Listing 31.21, respectively, were captured using Fiddler after sorting by the Name column in the Feedback.aspx page.

I’m including the full request and response traffic to make a point. No one would describe either the request or response as tiny. A lot of text must be passed back and forth from the browser to the server and back again when an UpdatePanel control refreshes its content.

A big chunk of both the request and response consists of ViewState. ViewState is passed to the server during an asynchronous postback, just like it is passed during a normal postback. The server-side page executes just like it executes during a normal postback. Therefore, the server-side page needs the ViewState to execute correctly.

In order to improve the performance of asynchronous postbacks performed by an UpdatePanel, consider disabling ViewState for the controls contained within the UpdatePanel. Every ASP.NET control has an EnableViewState property. You can always set this property to the value False in order to disable ViewState.

The following table compares the size of the asynchronous request and response with the GridView control’s ViewState enabled and disabled:

Ajax Request/Response Size

 

ViewState Enabled

ViewState Disabled

Request

2,066

1,067

Response

5,720

4,719

As the table clarifies, you save about a thousand bytes for both the request and response by disabling ViewState.

The disadvantage of disabling ViewState for a control such as a GridView is that it forces the GridView to make a new database call whenever you sort or page the GridView. However, one easy way to reduce the load on your database server is to take advantage of caching. If you cache all the records displayed by the GridView on the server, and disable ViewState, then you reduce your network traffic and you don’t place any additional load on your database server.

Note

To learn more about caching, see Chapter 25, “Caching Application Pages and Data.”

When working with the UpdatePanel, you should never forget that the server-side page undergoes its normal page execution lifecycle whenever an asynchronous postback occurs. If you perform an expensive database lookup in your Page_Load() method, that lookup will occur with each asynchronous call to your server.

You can avoid performing unnecessary server-side work during an asynchronous postback by taking advantage of the ScriptManager control’s IsInAsyncPostBack property. You can use this property to detect whether the page is executing in the context of a normal postback or an asynchronous postback.

Using the Timer Control

The ASP.NET AJAX Timer control enables you to refresh an UpdatePanel (or the entire page) on a timed basis. The Timer control has one important property:

  • IntervalThe amount of time, in milliseconds, between Tick events. The default value is 60,000 (1 minute).

The Timer control raises a Tick event every so many milliseconds, depending on the value of its Interval property.

If you don’t associate the Timer control with an UpdatePanel, the Timer posts the entire page back to the server performing a normal postback. For example, the page in Listing 31.22 posts the entire page back to the server every 2 seconds.

Example 31.22. TimerPage.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Timer Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <asp:Timer ID="Timer1" Interval="2000" runat="server" />

    The time is <%= DateTime.Now.ToString("T") %>

    </div>
    </form>
</body>
</html>

A more typical use of the Timer control is to refresh an UpdatePanel control’s content on a timed basis. For example, the page in Listing 31.23 displays a random quotation every 2 seconds (see Figure 31.14).

Refreshing the control content using Timer control.

Figure 31.14. Refreshing the control content using Timer control.

Example 31.23. TimerQuote.aspx

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void Page_Load(object sender, EventArgs e)
    {
        List<string> quotes = new List<string>();
        quotes.Add("A fool and his money are soon parted");
        quotes.Add("A penny saved is a penny earned");
        quotes.Add("An apple a day keeps the doctor away");

        Random rnd = new Random();
        lblQuote.Text = quotes[rnd.Next(quotes.Count)];
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Timer Quote</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <asp:Timer ID="Timer1" Interval="2000" runat="server" />
    Page Time: <%= DateTime.Now.ToString("T") %>

    <fieldset>
    <legend>Quote</legend>
    <asp:UpdatePanel ID="up1" runat="server">
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
    </Triggers>
    <ContentTemplate>
        <asp:Label ID="lblQuote" runat="server" />
    </ContentTemplate>
    </asp:UpdatePanel>
    </fieldset>

    </div>
    </form>
</body>
</html>

Notice that the Timer control in Listing 31.23 is configured as a trigger for the UpdatePanel control. When the Timer raises its Tick event, the UpdatePanel control refreshes its content by performing an asynchronous postback and grabbing a new quotation to display.

The final example of the Timer control is contained in Listing 31.24. In this example, a Timer control is used to refresh a discussion forum’s messages every 5 seconds. If you leave your browser window open, you’ll see new messages as they are posted by other members of the forum (see Figure 31.15).

Database messages being updated asynchronously.

Figure 31.15. Database messages being updated asynchronously.

Example 31.24. TimerMessages.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Timer Messages</title>
    <style type="text/css">

    .message
    {
        margin-left: 20px;
        font-style:italic;
    }

    </style>
</head>
<body>
    <form id="form1" runat="server">

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

    <asp:Timer ID="Timer1" Interval="5000" runat="server" />

    <asp:UpdatePanel ID="up1" runat="server">
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
    </Triggers>
    <ContentTemplate>
    Last Refresh <%= DateTime.Now.ToString("T") %>
    <hr />
    <asp:ListView
        id="lstMessages"
        DataSourceID="srcMessages"
        Runat="server">
        <LayoutTemplate>
            <div id="itemContainer" runat="server">
            </div>
        </LayoutTemplate>
        <ItemTemplate>
            <div>
                Posted by <%# Eval("PostedBy") %>
                <div class="message">
                <%# Eval("Post") %>
                </div>
            </div>
        </ItemTemplate>
    </asp:ListView>
    </ContentTemplate>
    </asp:UpdatePanel>

    <asp:ObjectDataSource
        id="srcMessages"
        TypeName="Message"
        SelectMethod="Select"
        Runat="server" />

    </form>
</body>
</html>

The page in Listing 31.24 contains a ListView that gets refreshed every 5 seconds. Be aware that each and every person who has this page open in a browser will cause a database call to be made every 5 seconds. This data is an excellent candidate for caching.

Using the UpdateProgress Control

The very last control that we need to examine in this chapter is the UpdateProgress control. This control enables you to display a progress indicator while an UpdatePanel is updating its content.

During a normal postback, the browser displays its progress in downloading new content by spinning an icon or displaying a progress bar. During an asynchronous postback, on the other hand, there is no visual indication of progress. You can use the UpdateProgress control to give the users some sense that something is happening during an asynchronous postback.

Note

In the next chapter, we examine an alternative method of displaying UpdatePanel progress. You learn how to use the UpdatePanelAnimation control to display an animation while an UpdatePanel’s content is being refreshed.

The page in Listing 31.25 illustrates how to use the UpdateProgress control. If you click the button, an animation spins while the asynchronous postback is performed (see Figure 31.16).

Viewing a spinning asynchronous progress indicator.

Figure 31.16. Viewing a spinning asynchronous progress indicator.

Example 31.25. ShowUpdateProgress.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        System.Threading.Thread.Sleep(5000);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show UpdateProgress</title>
    <style type="text/css">
    .progress
    {
        font-family:Arial;
        position: absolute;
        background-color:lightyellow;
        border:solid 2px red;
        padding:5px;
    }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <asp:UpdatePanel ID="up1" runat="server">
        <ContentTemplate>
            <%= DateTime.Now.ToString("T") %>
            <asp:Button
                id="btnSubmit"
                Text="Submit"
                Runat="server" OnClick="btnSubmit_Click" />
        </ContentTemplate>
        </asp:UpdatePanel>
        <asp:UpdateProgress
            ID="progress1"
            AssociatedUpdatePanelID="up1"
            runat="server">
            <ProgressTemplate>
                <div class="progress">
                <asp:Image
                    id="imgProgress"
                    ImageUrl="~/Images/Progress.gif"
                    Runat="server" />
                    Retrieving content...
                </div>
            </ProgressTemplate>
        </asp:UpdateProgress>

    </div>
    </form>
</body>
</html>

When you click the button in Listing 31.25, the response is delayed for 5 seconds so you have a chance to see the progress indicator. The delay simulates a network delay.

Note

Several websites enable you to generate fancy animator progress indicator icons. Here is the address to one of my favorites:

http://www.ajaxload.info

The UpdateProgress control supports the following three properties:

  • AssociatedUpdatePanelIDThe UpdateProgress control displays progress for this UpdatePanel control.

  • DisplayAfterThe amount of time, in milliseconds, before the UpdateProgress control displays content. The default is 500 milliseconds (half a second).

  • DynamicLayoutWhen this property is set to true (the default), the UpdateProgress control is initially hidden with the Cascading Style Sheet attribute display:none. When this property is set to false, the UpdateProgress control is hidden with the Cascading Style Sheet attribute visibility:hidden.

Summary

In this chapter, you learned how to use the primary server-side ASP.NET AJAX control: the UpdatePanel control. The bulk of this chapter was devoted to discussing the different features of this control. You learned how to specify triggers for an UpdatePanel. You also learned about how the UpdatePanel control participates in a page’s server-side and client-side page execution lifecycle. We also examined how you can handle errors gracefully when using the UpdatePanel control.

In the final parts of this chapter, you learned how to use two controls that support the UpdatePanel control. First, you learned how to use the Timer control to refresh an UpdatePanel on a timed basis. Second, you learned how to use the UpdateProgress control to give the user something to watch during an UpdatePanel control’s asynchronous postback.

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

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