Using Server-Side ASP.NET AJAX


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.

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

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.

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

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.

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:

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

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

LISTING 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 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. The page in Listing 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.

LISTING 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 is exactly the same as the page in Listing, except for two changes. First, the page now contains a ScriptManager control. Second, and more importantly, the DropDownList controls in Listing are wrapped inside an UpdatePanel control.

LISTING 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”
CHAPTER 31 Using Server-Side ASP.NET AJAX Runat=”server”>
<SelectParameters>
<asp:ControlParameter Name=”State” ControlID=”ddlState” />
</SelectParameters>
</asp:SqlDataSource>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>

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 represents a simple customer feedback form. 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.

LISTING 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 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:
ChildrenAsTriggers—Gets or sets a Boolean value that indicates whether child controls should trigger an asynchronous postback automatically.
ContentTemplateContainer—Gets the container for the UpdatePanel control’s ContentTemplate. You can add controls to the ContentTemplate programmatically using this property.
IsInPartialRendering—Gets a Boolean value indicating whether the UpdatePanel is being rendered in response to an asynchronous postback.
RenderMode—Gets 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.
Triggers—Gets a list of controls that trigger the UpdatePanel to perform either an asynchronous or synchronous postback.
UpdateMode—Gets 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.

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 contains two nested UpdatePanels. The outer UpdatePanel contains a DropDownList, FormView, and ListView control. The inner UpdatePanel contains only the ListView control.

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

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

LISTING 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 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!”.

LISTING 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 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 demonstrates how you can add JavaScript from the server to a page when performing an asynchronous postback.

LISTING 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, 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 logs each server event and displays the log in a BulletedList control.

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

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.init—Raised when a page is first requested. This event is not raised during an asynchronous postback.
PageRequestManager.initializeRequest—Raised before an asynchronous request to the server starts.
PageRequestManager.beginRequest—Raised before an asynchronous request to the server starts.
PageRequestManager.pageLoading—Raised after an asynchronous response is received from the server but before UpdatePanel content is updated.
PageRequestManager.pageLoaded—Raised after an asynchronous response is received from the server and after UpdatePanel content is updated. Also raised during the initial page request.
Application.load—Raised during both normal and asynchronous postbacks.
PageRequestManager.endRequest—Raised after an asynchronous response both when there is and when there isn’t an error.
Application.unload—Raised 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.

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 illustrates how to cancel an asynchronous postback in the event handler for the PageRequestManager.initializeRequest event.

LISTING 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 contains client-script that always gives precedence to the btnSubmit1 button over any other button that causes an asynchronous postback in the page

LISTING 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. For example, the page in Listing 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.

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.

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

LISTING 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. The title and heading are updated by passing a data item that represents the movie title during the asynchronous postback.

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 throws an exception when you click the button located in the UpdatePanel control. If you open the page in Listing  with a custom error page enabled, the browser is redirected to the ErrorPage.aspx page automatically.

LISTING 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

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

LISTING 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 theScriptManager 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 contains a typical Ajax request, and Listing contains a typical Ajax response.

LISTING Ajax Request
sm1=up1%7CgrdFeedback&__EVENTTARGET=grdFeedback&__EVENTARGUMENT=Sort%24Name&__VIEWS TATE=%2FwEPDwUKLTk4MzMyODc2MQ9kFgICAw9kFgICAw9kFgJmD2QWBAIBDzwrAAoBAA8WBB4LXyFEYXRh Qm91bmRnHgtfIUl0ZW1Db3VudGZkFgJmD2QWBGYPDxYCHgdWaXNpYmxlaGRkAgIPDxYCHwJoZGQCAw88KwA
NAgAPFgQfAGcfAQIEZAwUKwAEFggeBE5hbWUFAklkHgpJc1JlYWRPbmx5aB4EVHlwZRkrAR4JRGF0YUZpZW
xkBQJJZBYIHwMFBE5hbWUfBGgfBRkrAh8GBQROYW1lFggfAwUHQ29tbWVudB8EaB8FGSsCHwYFB0NvbW1lb
nQWCB8DBQ1EYXRlU3VibWl0dGVkHwRoHwUZKVxTeXN0ZW0uRGF0ZVRpbWUsIG1zY29ybGliLCBWZXJzaW9u
PTIuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OR8GBQ1
EYXRlU3VibWl0dGVkFgJmD2QWCgIBD2QWCGYPDxYCHgRUZXh0BQE0ZGQCAQ8PFgIfBwUFU3RldmVkZAICDw8WAh8HBRJIZXJlIGlzIG15IGNvbW1lbnRkZAIDDw8WA
8HBRQxMC8zLzIwMDcgNDo1MjowNCBQTWRk-
AgIPZBYIZg8PFgIfBwUBM2RkAgEPDxYCHwcFA0JvYmRkAgIPDxYCHwcFFUhleSwgd2hhdCBhYm91dCBBamF
4P2RkAgMPDxYCHwcFFDEwLzMvMjAwNyA0OjE5OjI1IFBNZGQCAw9kFghmDw8WAh8HBQExZGQCAQ8PFgIfBwUFc3RldmVkZAICDw8WAh8HBRVXaGF0IGEgZ3JlYXQgd
Vic2l0ZSFkZAIDDw8WAh8HBRQxMC8zLzIwMDcgNDowOTo1NiBQTWRkAgQPZBYIZg8PFgIfBwUBMmRkAgEPDxYCHwcFBXN0ZXZlZGQCAg8PFgIfBwVaV293L
CBpdCBpcyB3cml0dGVuIGVudGlyZWx5IHdpdGggTGlucT8gVGhhdCBtdXN0IGhhdmUgc2F2ZWQgeW91IGEg
bG90IG9mIGRldmVsb3BtZW50IHRpbWUhZGQCAw8PFgIfBwUUMTAvMy8yMDA3IDQ6MDk6NTYgUE1kZAIFDw8WAh8CaGRkGAIFC2dyZEZlZWRiYWNrDzwrAAkCBAUHQ2
tbWVudAgCAWQFC2ZybUZlZWRiYWNrDx-
QrAAdkZAICZGQWAGRkuZs7yL%2Fem%2BLQG%2FRqUcYBa9aTsI4%3D&frmFeedback%24txtName=&frmFe
edback%24txtComment=&__EVENTVALIDATION=%2FwEWCALS%2BMLfAgKVvojNBgKio6JkAp7t150BAtnw
1uUHApCu1%2B4GAoKl7PcLAoGY7eABIj9XtltK55e8Og9%2BNK4DglwM43M%3D&

LISTING 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|hidden-
Field|__VIEWSTATE|/wEPDwUKLTk4MzMyODc2MQ9kFgICAw9kFgICAw9kFgJmD2QWBAIBDzwrAAoBAA8WB
B4LXyFEYXRhQm91bmRnHgtfIUl0ZW1Db3VudGZkFgJmD2QWBGYPDxYCHgdWaXNpYmxlaGRkAgIPDxYCHwJoZGQCAw88KwANAgAPFgQfAGcfAQIEZAwUKwAEFggeBE5
bWUFAklkHgpJc1JlYWRPbmx5aB4EVHlwZRkr
AR4JRGF0YUZpZWxkBQJJZBYIHwMFBE5hbWUfBGgfBRkrAh8GBQROYW1lFggfAwUHQ29tbWVudB8EaB8FGSs
CHwYFB0NvbW1lbnQWCB8DBQ1EYXRlU3VibWl0dGVkHwRoHwUZKVxTeXN0ZW0uRGF0ZVRpbWUsIG1zY29ybG
liLCBWZXJzaW9uPTIuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYx-
OTM0ZTA4OR8GBQ1EYXRlU3VibWl0dGVkFgJmD2QWCgIBD2QWCGYPDxYCHgRUZXh0BQEzZGQCAQ8PFgIfBwUDQm9iZGQCAg8PFgIfBwUVSGV5LCB3aGF0IGFib3V0IE
qYXg/ZGQCAw8PFgIfBwUUMTAvMy8yMDA3IDQ6
MTk6MjUgUE1kZAICD2QWCGYPDxYCHwcFATFkZAIBDw8WAh8HBQVzdGV2ZWRkAgIPDxYCHwcFFVdoYXQgYSBncmVhdCB3ZWJzaXRlIWRkAgMPDxYCHwcFFDEwLzMvMj
wNyA0OjA5OjU2IFBNZGQCAw9kFghmDw8WAh8HB
QEyZGQCAQ8PFgIfBwUFc3RldmVkZAICDw8WAh8HBVpXb3csIGl0IGlzIHdyaXR0ZW4gZW50aXJlbHkgd2l0
aCBMaW5xPyBUaGF0IG11c3QgaGF2ZSBzYXZlZCB5b3UgYSBsb3Qgb2YgZGV2ZWxvcG1lbnQgdGltZSFkZAIDDw8WAh8HBRQxMC8zLzIwMDcgNDowOTo1NiBQTWRkAg
PZBYIZg8PFgIfBwUBNGRkAgEPDxYCHwcFBVN0ZXZlZGQCAg8PFgIfBwUSSGVyZSBpcyBteSBjb21tZW50ZGQCAw8PFgIfBwUUMTAvMy8yMDA3IDQ6NTI
6MDQgUE1kZAIFDw8WAh8CaGRkGAIFC2dyZEZlZWRiYWNrDzwrAAkCBAUETmFtZQgCAWQFC2ZybUZlZWRiYWNrDxQrAAdkZAICZGQWAGRkVKO/
p/Z+TKr7wPvuagKWmQ2FfIY=|96|hiddenField|__EVENTVALIDATION|/
wEWCAKuyYyNBQKVvojNBgKio6JkAp7t150BAtnw1uUHApCu1+4GAoKl7PcLAoGY7eABqkyic8N4ML
Im8nwM1bpWblCsXyA=|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|Scrip
tPath|/Original/ScriptResource.axd?d=pGcnA3xf7SUaukdr-behbvslg2hOq48wA9WuXk0fdM20
k9xho9i9m9JZzVPbP2-5l3cHqVSeROczjHZXGFjpag2&t=633231592768281250|367|scriptBlock|
ScriptContentWithTags|{“text”:”\r\n\u003c!—\r\nvar Page_ValidationActive =
false;\r\nif (typeof(ValidatorOnLoad) == \”function\”) {\r\n ValidatorOn-
Load();\r\n}\r\n\r\nfunction ValidatorOnSubmit() {\r\n if (Page_ValidationActive)
{\r\n return ValidatorCommonOnSubmit();\r\n }\r\n else {\r\n
return true;\r\n }\r\n}\r\n// —\u003e\r\n”,”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_va
lName’)[‘evaluationfunction’]|”RequiredFieldValidatorEvaluateIsValid”|2|expando|doc
ument.getElementById(‘frmFeedback_valName’)[‘initialvalue’]|””|24|expando|docu1648
CHAPTER 31 Using Server-Side ASP.NET AJAX
ment.getElementById(‘frmFeedback_valComment’)[‘controltovalidate’]|”frmFeedback_txt
Comment”|39|expando|document.getElementById(‘frmFeedback_valComment’)[‘evaluationfunction’]|”
RequiredFieldValidatorEvaluateIsValid”|2|expando|document.getElement-
ById(‘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 and Listing, 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 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.

Using the Timer Control
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.

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:
Interval—The 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 posts the entire page back to the server every 2 seconds.

LISTING 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 displays a random quotation every 2 seconds.

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

LISTING 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 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. The page in Listing illustrates how to use the UpdateProgress control. If you click the button, an animation spins while the asynchronous postback is performed.

LISTING 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, the response is delayed for 5 seconds so you have a chance to see the progress indicator. The delay simulates a network delay. Several websites enable you to generate fancy animator progress indicator icons. Here is the address to one of my favorites:

The UpdateProgress control supports the following three properties:
AssociatedUpdatePanelID—The UpdateProgress control displays progress for this UpdatePanel control.
DisplayAfter—The amount of time, in milliseconds, before the UpdateProgress control displays content. The default is 500 milliseconds (half a second).
DynamicLayout—When 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.

No comments:

Post a Comment