Chapter 2: Partial Page Updates

Chapter 2: Partial Page Updates

If I had to sum up what AJAX is all about in a simple phrase, it would be “an improved experience for the end user through partial page updates and a better user interface.” At the core of the ASP.NET AJAX offering is the UpdatePanel control, which adds the ability to have portions of a page be updated asynchronously. The other half of the equation is richer user interface (UI) elements: controls that leverage the browser features better or extenders that enhance the functionality of other controls.

In this chapter, you will learn how to use the UpdatePanel control and also learn about its current limitations. In addition to working with the UpdatePanel in .NET code that runs on the server, you will also learn how to work with its client-side counterpart, the PageRequestManager, using JavaScript code running in the browser to get more fine-grained control over the asynchronous updates.

I will also demonstrate the UpdateProgress control, which gives the user a visual indication that something is happening in the background so he won’t lose patience with a page that stops responding momentarily, and I’ll show you the Timer control, which makes it easy to schedule updates to occur at regular intervals. Finally, the chapter covers the accessibility concerns of partial page rendering. Many developers put in a lot of effort to ensure that their web application works well with screen readers, and partial page rendering can make that task more difficult if it is not approached carefully.

The UpdatePanel Control

Usually, when a postback occurs, the entire page is reloaded. There is a visible flicker of the browser as the previous rendering is removed and the new rendering is displayed. The user will likely notice the interruption, and this can interfere with his train of thought. This is different from the smooth responsiveness we are accustomed to with desktop applications and can be disconcerting or annoying to users. This click-pause-flicker pattern is what the UpdatePanel helps you overcome. A web application can now be almost as responsive as a fat client Windows application! The small delays required for passing data between the client and the server are still there, but they are much easier to tolerate now. In many cases, the user may not even notice these delays, because we can distribute the updates so they don’t all occur at once.

The UpdatePanel is a container control without any UI of its own. It allows you to mark page regions as eligible to be refreshed independently. When a control within the UpdatePanel triggers a postback, the UpdatePanel intervenes to initiate the postback asynchronously and update just that portion of the page. The term asynchronously means that we don’t have to stop and wait for the result from the server. Rather, we can continue servicing the page with other JavaScript code, and the user can interact with other controls while we’re waiting for the response from the server. At the time we make the call to the server, we provide the name of a JavaScript callback function that will be called when the response has been received. That callback function will receive the results and update various page controls accordingly.

Listing 2-1 (BasicUpdatePanel.aspx) demonstrates the most basic usage of the UpdatePanel. The page has two labels and two buttons that can each cause a postback. It simply updates the two labels with the current server time. Button1 and its label are placed inside an UpdatePanel. Actually, they are put in the ContentTemplate of the UpdatePanel. Controls and markup placed in an UpdatePanel must reside in the ContentTemplate. The other label and button are left outside the UpdatePanel. There is also a ScriptManager control on the page. One instance of the ScriptManager must be placed on every page that uses AJAX. The ScriptManager control will be covered in detail in Chapter 5.

Listing 2-1
Image from book

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server" language="C#" >
    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        string theTime = DateTime.Now.ToLongTimeString();
        for (int i = 0; i < 3; i++) {
            theTime += "<br />" + theTime;
        }
        time1.Text = theTime;
        time2.Text = theTime;
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Basic Update Panel</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <asp:Label runat="server" ID="time1"></asp:Label><br /><br />
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
   <ContentTemplate>
       <div style="border-style:solid;background-color:gray;">
         <asp:Label runat="server" ID="time2"></asp:Label><br />
         <asp:Button ID="Button1" runat="server" Text="Inside Button" />
         </div><br />
   </ContentTemplate>
</asp:UpdatePanel>
    <asp:Button ID="Button2" runat="server" Text="Outside Button" />
</div>
</form>
</body>
</html>
Image from book

In the code-behind, both labels are assigned the same time string in the same way. When the page is first requested, the two labels show the same time. This is shown in Figure 2-1.

Image from book
Figure 2-1

When the Outside Button is clicked, a postback occurs and the time is updated for both of the labels. But when the button inside the UpdatePanel is clicked, an asynchronous postback occurs and only the label inside the UpdatePanel is updated. There is no flash or flicker as the content is changed with new data from the server. The result of clicking the Inside Button is shown in Figure 2-2. The time in the UpdatePanel has been updated, but the other time display has not.

Image from book
Figure 2-2

This highlights a key point about the UpdatePanel: When using the UpdatePanel, the page executes just as though it were a regular postback. You saw that there was no code on the page that did anything specific for partial page updates. The code never made reference to the UpdatePanel, and yet only part of the page changed. The UpdatePanel gathers the rendering from controls within the UpdatePanel and abandons most of the rest of the output. It also returns the updated hidden fields with things like ViewState that need to reflect the new state of the page. But the other controls’ output is not reflected in what the user sees.

Triggering Updates

The default behavior you saw for Listing 2-1 is for the UpdatePanel content to be refreshed when the child controls would normally trigger a postback. This is because the default value for the UpdateMode is Always, and the default value of the ChildrenAsTriggers property is true.

The UpdateMode property is of the type UpdatePanelUpdateMode. The two possible values are Always and Conditional. The default mode of Always means that the UpdatePanel will be refreshed anytime a child control initiates a postback. This corresponds to the ChildrenAsTriggers property’s default value of true. An UpdatePanel is triggered to refresh, and with the default settings, the child control’s postback triggers the update. In fact, if you set ChildrenAsTriggers to false without changing the UpdateMode value, you get the error shown in Figure 2-3. The following table summarizes the results of possible combinations of UpdateMode and ChildrenAsTriggers.

Image from book
Figure 2-3
Open table as spreadsheet

UpdateMode

ChildrenAsTriggers

Result

Always

false

Illegal parameters

Always

true

UpdatePanel refreshes if whole page refreshes or when a child control on the panel posts back

Conditional

false

UpdatePanel refreshes if whole page refreshes or when a trigger control outside the panel initiates a refresh

Conditional

true

UpdatePanel refreshes if whole page refreshes or when a child control on the panel posts back or when a trigger control outside the panel initiates a refresh

You can’t set ChildrenAsTriggers to false while the UpdateMode is set to Always. The two are contradictory, as you would be asking the UpdatePanel to refresh based on the child controls while simultaneously saying that the child controls should not trigger updates. The UpdateMode needs to be set to its other possibility: Conditional.

When the UpdateMode is Conditional and the ChildrenAsTriggers property is false, the UpdatePanel will not refresh unless the whole page is updated or when a control outside the UpdatePanel is defined as the trigger and initiates a postback. Listing 2-2 (Triggers.aspx) demonstrates this. I modified the previous listing to contain two UpdatePanel controls, each with a label that displays time retrieved from the server. The buttons are moved to the top of the page, outside of both of the UpdatePanel controls. The UpdateModel is set to Conditional. I also set ChildrenAsTriggers to false on one of the UpdatePanels, but in reality it is completely unnecessary in this example. There aren’t any child controls that would cause a postback, so the setting does not affect the behavior.

Listing 2-2
Image from book
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server" language="C#" >
    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        string theTime = DateTime.Now.ToLongTimeString();
        for (int i = 0; i < 3; i++) {
            theTime += "<br />" + theTime;
        }
        time1.Text = theTime;
        time2.Text = theTime;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Triggers</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <asp:Button ID="Button1" runat="server" Text="Update One" />
    <asp:Button ID="Button2" runat="server" Text="Update the Other" /><br /><br />

<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional" >
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
</Triggers>
<ContentTemplate>
    <div style="border-style:solid;background-color:gray;">

    <asp:Label runat="server" ID="time1"></asp:Label><br /><br />
    </div><br />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional" 
ChildrenAsTriggers="false" >
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button2" EventName="Click" />
</Triggers>
<ContentTemplate>
    <div style="border-style:solid;background-color:gray;">
    <asp:Label runat="server" ID="time2"></asp:Label><br />
    </div><br />
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>
Image from book

If I stopped here, the buttons would simply cause a full postback. There would be no partial updates. But inside the UpdatePanels, I have added a Triggers element with an AsyncPostBackTrigger control. This is where you define what controls should cause the UpdatePanel to be refreshed. The ControlID defines the source control and the EventName declares when to trigger.

Although demonstrating a dynamic effect on paper can be a challenge, Figure 2-4 shows the result.

Image from book
Figure 2-4

Although the majority of scenarios when using the UpdatePanel call for the update to be done asynchronously, in some cases you will want to have a full postback occur. You can make exceptions to the partial page update behavior by defining a PostBackTrigger. The event name does not need to be defined. Instead, you just provide the ControlID:

<Triggers>
<asp:PostBackTrigger ControlID="Button2" />
</Triggers>
Important 

UpdatePanels can be nested inside one another. When the outer UpdatePanel is refreshed, the inner UpdatePanel will also be refreshed. But when the inner UpdatePanel is updated, the outer UpdatePanel is not affected. Even when the outer UpdatePanel has ChildrenAsTriggers set to true, an asynchronous update of the inner UpdatePanel will not cause the outer UpdatePanel to be refreshed. To have controls in a contained UpdatePanel cause a refresh, they have to be set explicitly as triggers on the outer UpdatePanel.

Calling the Update Method from the Server

So far, the examples have all been about controlling partial page updates from within the browser. The UpdatePanel also provides an Update method that allows a region of the page to be refreshed via code running on the server. During a partial page update, remember that the page runs normally and the UpdatePanel controls are just updating without the user seeing a full page postback. Any condition during the page execution can be reason to Update a portion of the page.

In Listing 2-3 (CallingUpdate.aspx) , the page code updating the time is changed to conditionally call Update on the second UpdatePanel when the first is being refreshed. The current time is stored in Session on each call. When the page executes, the current time is checked against the time stored previously in Session. If more than four seconds have elapsed, Update is called. The refreshed view of that portion of the page is then also updated when the call completes and the first UpdatePanel is updated.

Listing 2-3
Image from book
<!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">
<script runat="server" language="C#">
    protected override void  OnLoad(EventArgs e) {
        object tempTime = Session["LastTime"];
        if (tempTime != null) {
            DateTime lastTime = (DateTime)tempTime;
            if (lastTime != null) {
                if (DateTime.Now > lastTime.AddSeconds(4)) {
                    UpdatePanel2.Update();
                    Session["lastTime"] = DateTime.Now;
               }
            }
        }
        else {
            Session["lastTime"] = DateTime.Now;
        }

     base.OnLoad(e);
        string theTime = DateTime.Now.ToLongTimeString();
        for(int i = 0; i < 3; i++) {
            theTime += "<br />" + theTime;
        }

        time1.Text = theTime;
        time2.Text = theTime;
    }
</script>
    <title>ASP.NET AJAX Calling Update</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <asp:Button ID="Button1" runat="server" Text="Update Conditionally" />

<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional" >
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
</Triggers>
<ContentTemplate>
    <div style="border-style:solid;background-color:gray;" >
    <asp:Label runat="server" ID="time1"></asp:Label><br /><br />
    </div><br />
</ContentTemplate>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional" >
<ContentTemplate>
    <div style="border-style:solid;background-color:gray;" >
    <asp:Label runat="server" ID="time2"></asp:Label><br />
    </div><br />
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>
Image from book
Tip 

Although the UpdatePanel does not have a visible rendering of its own, it does wrap the rendering from the ContentTemplate in an HTML element. The default is to use a <div> element, but you can switch to use a <span> element instead. This is controlled through the RenderMode property. Setting the RenderMode to block results in the rendering being placed inside a div, and setting it to inline indicates that a span should be used.

The Partial Page Update Lifecycle

The page execution lifecycle is not altered by the partial rendering feature. The ScriptManager participates in the lifecycle to facilitate the partial page updates. It coordinates gathering the renderings from the UpdatePanels that need to be refreshed during an asynchronous post and carrying the hidden fields necessary to make the following post function correctly. Controls that modify the ViewState, even if they are not in the UpdatePanel being affected, do not have to take any special action to ensure the change is available in subsequent requests. Event validation, cross page posting, and ASP.NET’s ability to maintain the scroll position all continue to work when using the UpdatePanel.

When using the UpdatePanel, it is key to remember that the page lifecycle is the same. It’s nice that no special pains are required to start taking advantage of partial page updates, but don’t forget that the overhead on the server is the same and that the traffic between browser and server can still be significant. For example, if you have a GridView or other data control on the page that is making use of ViewState to avoid trips to the database, that ViewState will be carried with each request even when the data is not being updated. As this ViewState baggage can slow down partial page updates, you should carefully consider which controls need to have ViewState enabled, and you should disable ViewState on any particular controls where it isn’t needed.

There are several ways to determine whether your code is executing in the context of a partial page update. For the control author to find information about the current state, he can walk up the control tree to find if the controls are contained within an UpdatePanel and query the IsInPartialRendering property. This property reveals if the UpdatePanel is rendering back to the browser with asynchronous updates in order to take custom actions if desired. For the page author who just wants to know if the page is executing as the result of an UpdatePanel initiated asynchronous post, the ScriptManager has the information. Check the IsInAsyncPostBack property to make the determination:

if (ScriptManager1.IsInAsyncPostBack)

UpdatePanel Cautions and Complexities

Not every control will work well with the UpdatePanel. The ScriptManager provides new versions of APIs for control authors to register JavaScript for use in the browser. Complex controls that make heavy use of dynamically registered script won’t work correctly until they are updated to switch from using the ClientScriptManager of ASP.NET 2.0 to using the ScriptManager included with ASP.NET AJAX. For example, the ASP.NET Menu and TreeView controls are not fully compatible with the UpdatePanel in the 1.0 release. Those controls both already do out-of-band communication with the server, so there is less impetus to put them inside an UpdatePanel.

The validation controls also register script in a way that is incompatible with the UpdatePanel. An update is available from Microsoft to correct the problem. You can also disable the client script for the validator controls and then use them in an UpdatePanel:

<asp:RequiredFieldValidator EnableClientScript="false" runat="server"
ControlToValidate="TextBox1" />

With EnableClientScript set to false, the validator controls no longer perform any client-side validation. Instead, the errors must be caught on the server explicitly by your code. (By the way, because the client can disable or modify JavaScript running in the browser, you should always validate incoming values on the server anyway.) More details as well as the source code for an updated set of validator controls is available on the ASP.NET Forums at http://forums.asp.net/thread/1545781.aspx.

Another complication with the set of supported controls for use with the UpdatePanel is Web Parts. Until an update is made to Web Parts and made available by Microsoft, you cannot simply place a Web Part inside an UpdatePanel and have it refresh asynchronously.

The GridView and DetailsView controls already have a feature for using client callbacks to paging and sorting. It is not enabled by default, but if the feature is enabled, these controls cannot be used inside an UpdatePanel. The FileUpload control also can’t submit files as part of an asynchronous postback. Instead, to have an UpdatePanel contain a FileUpload, the control that triggers the upload should be configured to cause a full postback using a PostBackTrigger:

<asp:UpdatePanel ID="UpdatePanel" runat="server" UpdateMode="Conditional" >
<ContentTemplate>
    <asp:FileUpload runat="server" ID="fileUpload" />
    <asp:Button runat="server" Text="Upload" ID="UploadButton" />
</ContentTemplate>
<Triggers>
<asp:PostBackTrigger ControlID="UploadButton" />
</Triggers>
</asp:UpdatePanel>

The asp:substitution control presents complications that haven’t been addressed with the ASP.NET AJAX 1.0 release and prevents the substitution from working inside an UpdatePanel. The 1.0 release is built on top of ASP.NET 2.0, and given the complexity of the caching infrastructure, it may require changes to the core to enable the Substitution control to work with partial page rendering.

And finally, because of the way the ScriptManager extracts the contents needed for the partial page updates in the 1.0 release, calling Response.Write during the execution of an asynchronous postback will result in an error. This is expected to be addressed in a future release or update to the 1.0 release.