AJAX is not a particular technology or product. It
refers to a number of client features, and related development
techniques, that make Web applications look like desktop applications.
AJAX doesn’t require any plug-in modules either and is not browser
specific. Virtually any browser released in the past five years can
serve as a great host for AJAX-based applications. AJAX development
techniques revolve around one common software element—the XMLHttpRequest
object. The availability of this object in the object model of most
browsers is the key to the current ubiquity and success of AJAX
applications. In addition to XMLHttpRequest,
a second factor contributes to the wide success of AJAX—the
availability of a rich document object model in virtually any browser.
Based
on this quick assay of the AJAX paradigm, the programming model of AJAX
applications seem to be clear and unquestionable. You write code that
captures client-side events, conduct an operation on the server via XMLHttpRequest,
get the results, and update the user interface. All the client-side
programming is done through JavaScript. This model is a real
performance booster when applied to individual features or bottlenecks
in existing pages, but it’s hard to scale to a large application
because it proves quite expensive in terms of skills to acquire and
time to implement.
It
is the downside of the loudly requested change of paradigm for Web
applications. When it comes to rewriting Web applications for AJAX,
nearly all aspects of the application need to be redesigned,
refactored, and rewritten. Opting for AJAX all the way might be too
much for too many companies; and it’s not a step to take with a light
heart.
Today
you do much of your ASP.NET programming using server controls. A server
control normally emits HTML markup. In an AJAX scenario, a server
control emits markup plus some script code to support AJAX requests.
This is not exactly the loudly requested change of paradigm, but it is
a good compromise between the today’s Web and AJAX. Most third-party
vendors prepared their own offering based on this idea. They just
provide you with a new set of controls that supply a server and client
programming model and manage any browser-to-server communication for
you.
But what if you
aren’t using any third-party library? Should you write new AJAX-enabled
controls yourself? An AJAX server control can be the AJAX version of a
traditional server control—for example, an AJAX-enabled drop-down list
that supports client insertions and moves them back to the server
without a full-page postback. But it can also be a generic control
container that takes care of refreshing all of its children without a
full-page postback. Enter partial rendering.
ASP.NET partial rendering works according to this idea. It provides a new container control—the UpdatePanel
control—that you use to surround portions of existing pages, or
portions of new pages developed with the usual programming model of
ASP.NET 2.0. A postback that originates within any of these updatable
regions is managed by the UpdatePanel control and updates only the controls in the region.
The UpdatePanel Control
In ASP.NET AJAX, partial rendering is the programming technique centered around the UpdatePanel control. In ASP.NET, the UpdatePanel
control represents the shortest path to AJAX. It allows you to add
effective AJAX capabilities to sites written according to the classic
programming model of ASP.NET 2.0. As a developer, you have no new
skills to learn, except the syntax and semantics of the UpdatePanel
control. The impact on existing pages is very limited, and the exposure
to JavaScript is very limited, and even null in most common situations.
You
might wonder how partial rendering differs from classic postbacks. The
difference is in how the postback is implemented—instead of letting the
browser perform a full-page refresh, the UpdatePanel
control intercepts any postback requests and sends an out-of-band
request for fresh markup to the same page URL. Next, it updates the DOM
tree when the response is ready. Let’s investigate the programming
interface of the control.
The UpdatePanel Control at a Glance
The UpdatePanel control is a container control defined in the System.Web.Extensions assembly. It belongs specifically to the System.Web.UI namespace. The control class is declared as follows:
public class UpdatePanel : Control
{
...
}
Although it’s logically similar to the classic ASP.NET Panel control, the UpdatePanel control differs from the classic panel control in a number of respects. In particular, it doesn’t derive from Panel
and, subsequently, it doesn’t feature the same set of capabilities as
ASP.NET panels, such as scrolling, styling, wrapping, and content
management.
The UpdatePanel control derives directly from Control,
meaning that it acts as a mere AJAX-aware container of child controls.
It provides no user-interface-related facilities. Any required styling
and formatting should be provided through the child controls. In
contrast, the control sports a number of properties to control page
updates and also exposes a client-side object model. Consider the
following classic ASP.NET code:
<asp:GridView ID="GridView1" runat="server"
DataSourceID="ObjectDataSource1"
AllowPaging="True"
AutoGenerateColumns="False" Width="450px">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID">
<ItemStyle Width="70px" />
</asp:BoundField>
<asp:BoundField DataField="CompanyName" HeaderText="Company">
<ItemStyle Width="300px" />
</asp:BoundField>
<asp:BoundField DataField="Country" HeaderText="Country">
<ItemStyle Width="80px" />
</asp:BoundField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
TypeName="Core35.DAL.Customers"
SelectMethod="LoadAll" />
This
code causes a postback each time you click to view a new page, edit a
record, or sort by a column. As a result, the entire page is redrawn
even though the grid is only a small fragment of it. With partial
rendering, you take the preceding markup and just wrap it with an UpdatePanel control, as shown here:
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
...
</ContentTemplate>
</asp:UpdatePanel>
In addition, you need to add a ScriptManager
control to the page. That’s the essence of partial rendering. And it
magically just works. Well, not just magically, but it works.
Note
From
this simple but effective example, you might be led to think that it
suffices that you surround the whole body of the page with an UpdatePanel
control and you’re done. If you do, it certainly works. It might not be
particularly efficient though. In the worst case, you need the same
bandwidth as you do with classic ASP.NET; however, you still give your
users an infinitely better experience because only a portion of the
page actually refreshes. As
we’ll learn in the rest of the chapter, partial rendering offers a
number of attributes to optimize the overall behavior and performance.
However, the majority of users are more than happy with the sole effect
of a partial page rendering. |
The Programming Interface of the Control
Table 1 details the properties defined on the UpdatePanel control that constitute the aspects of the control’s behavior that developers can govern.
Table 1. Properties of the UpdatePanel Control
Property | Description |
---|
ChildrenAsTriggers | Indicates whether postbacks coming from child controls will cause the UpdatePanel to refresh. This property is set to true by default. When this property is false, postbacks from child controls are ignored. You can’t set this property to false when the UpdateMode property is set to Always. |
ContentTemplate | A template property, defines what appears in the UpdatePanel when it is rendered. |
ContentTemplateContainer | Retrieves
the dynamically created template container object. You can use this
object to programmatically add child controls to the UpdatePanel. |
IsInPartialRendering | Indicates
whether the panel is being updated as part of an asynchronous postback.
Note that this property is designed for control developers. Page
authors should just ignore it. |
RenderMode | Indicates whether the contents of the panel will be rendered as a block <div> tag or as an inline <span> tag. The feasible values for the property—Block and Inline—are defined in the UpdatePanelRenderMode enumeration. The default is Block. |
UpdateMode | Gets
or sets the rendering mode of the control by determining under which
conditions the panel gets updated. The feasible values—Always and Conditional—come from the UpdatePanelUpdateMode enumeration. The default is Always. |
Triggers | Defines a collection of trigger objects, each representing an event that causes the panel to refresh automatically. |
A bit more explanation is needed for the IsInPartialRendering read-only Boolean property. It indicates whether the contents of an UpdatePanel
control are being updated. From this description, it seems to be a
fairly useful property. Nonetheless, if you read its value from within
any of the handlers defined in a code-behind class, you’ll find out
that the value is always false.
As mentioned, IsInPartialRendering
is a property designed for control developers only. So it is assigned
its proper value only at rendering time—that is, well past the PreRender event you can capture from a code-behind class. Developers creating a custom version of the UpdatePanel control will likely override the Render
method. From within this context, they can leverage the property to
find out whether the control is being rendered in a full-page refresh
or in a partial rendering operation.
As
a page author, if you just need to know whether a portion of a page is
being updated as a result of an AJAX postback, you use the IsInAsyncPostBack Boolean property on the ScriptManager control.
Note
Like any other ASP.NET AJAX feature, partial rendering requires a ScriptManager control in the page. It is essential, though, that the EnablePartialRendering property on the manager be set to true—which is the default case. If this property is set to false, the UpdatePanel control works like a regular panel. |
Populating the Panel Programmatically
The content of an updatable panel is defined through a template property—the ContentTemplate property. Just like any other template property in ASP.NET controls, ContentTemplate can be set programmatically. Consider the following page fragment:
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<%-- Left empty deliberately. Will be filled out programmatically --%>
</asp:UpdatePanel>
In the PreInit event of the code-behind page, you can set the ContentTemplate programmatically, as shown here:
protected void Page_PreInit(object sender, EventArgs e)
{
// You could also read the URL of the user control from a configuration file
string ascx = "customerview.ascx";
UpdatePanel1.ContentTemplate = this.LoadTemplate(ascx);
}
You are not allowed to set the content template past the PreInit
event. However, at any time before the rendering stage, you can add
child controls programmatically. In ASP.NET, to add or remove a child
control, you typically use the Controls property of the parent control, as shown here:
UpdatePanel1.Controls.Add(new LiteralControl("Test"));
If you try to add a child control programmatically to the Controls collection of an UpdatePanel—as in the preceding code snippet—all that you get is a runtime exception. You should use the ContentTemplateContainer property instead. The reason is that what you really want to do is add or remove controls to the content template, not to the UpdatePanel directly. That’s why Controls doesn’t work and you have to opt for the actual container of the template. The following code shows how to populate the content template programmatically:
public partial class Samples_Ch19_Partial_Dynamic : System.Web.UI.Page
{
private Label Label1;
protected void Page_Load(object sender, EventArgs e)
{
UpdatePanel upd = new UpdatePanel();
upd.ID = "UpdatePanel1";
// Define the button
Button button1 = new Button();
button1.ID = "Button1";
button1.Text = "What time is it?";
button1.Click += new EventHandler(Button1_Click);
// Define the literals
LiteralControl lit = new LiteralControl("<br>");
// Define the label
Label1 = new Label();
Label1.ID = "Label1";
Label1.Text = "[time]";
// Link controls to the UpdatePanel
upd.ContentTemplateContainer.Controls.Add(button1);
upd.ContentTemplateContainer.Controls.Add(lit);
upd.ContentTemplateContainer.Controls.Add(Label1);
// Add the UpdatePanel to the list of form controls
this.Form.Controls.Add(upd);
}
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = DateTime.Now.ToShortTimeString();
}
}
You can add an UpdatePanel
control to the page at any time in the life cycle. Likewise, you can
add controls to an existing panel at any time. However, you can’t set
the content template programmatically past the page’s PreInit event.
Master Pages and Updatable Regions
You can safely use UpdatePanel
controls from within master pages. Most of the time, the use of
updatable panels is easy and seamless. There are a few situations,
though, that deserve a bit of further explanation.
If you add a ScriptManager
control to a master page, partial rendering is enabled by default for
all content pages. In addition, initial settings on the script manager
are inherited by all content pages. What if you need to change some of
the settings (for example, add a new script file or switch on script
localization) for a particular content page? You can’t have a new
script manager, but you need to retrieve the original one defined on
the master page.
In the content page, you can declaratively reference a ScriptManagerProxy and change some of its settings. The proxy retrieves the script manager currently in use and applies changes to it.
The ScriptManagerProxy
control, though, is mostly designed to let you edit the list of scripts
and services registered with the manager in a declarative manner, and
it doesn’t let you customize, say, error handling or script
localization. You can do the same (and indeed much more) by
programmatically referencing the script manager in the master page.
Here’s how:
protected void Page_Init(object sender, EventArgs e)
{
// Work around the limitations in the API of the ScriptManagerProxy control
ScriptManager.GetCurrent(this).EnableScriptLocalization = true;
}
In the content page, you create a handler for the page’s Init event, retrieve the script manager instance using the static GetCurrent method on the ScriptManager class, and apply any required change.
User Controls and Updatable Regions
User
controls provide an easy way to bring self-contained, auto-updatable
AJAX components into an ASP.NET page. Because each page can have at
most one script manager, you can’t reasonably place the script manager
in the user control. That would work and make the user control
completely self-contained, but it would also limit you to using exactly
one instance of the user control per page. On the other hand, the UpdatePanel
control requires a script manager. Multiple script managers, or the
lack of at least one script manager, will cause an exception.
The
simplest workaround is that you take the script manager out of the user
control and place it in the host page. User controls therefore assume
the presence of a script manager, and they use internally as many
updatable panels as needed:
<asp:ScriptManager runat="server" ID="ScriptManager1" />
<x:Clock runat="server" ID="Clock1" />
<hr />
<x:Clock runat="server" ID="Clock2" />
Note
You can’t call Response.Write from within a postback event handler (for example, Button1_Click)
that gets called during an asynchronous AJAX postback. If you do so,
you’ll receive a client exception stating that the message received
from the server could not be parsed. In general, calls to Response.Write—but also response filters, HTTP modules, or server tracing (Trace=true)—modify the stream returned to the client by adding explicit data that alters the expected format. |
Optimizing the Usage of the UpdatePanel Control
Partial
rendering divides the page into independent regions, each of which
controls its own postbacks and refreshes without causing, or requiring,
a full-page update. This behavior is desirable when only a portion—and
perhaps only a small portion—of the page needs to change during a
postback. Partial updates reduce screen flickering and allow you to
create more interactive Web applications. An ASP.NET page can contain
any number of UpdatePanel controls.
An UpdatePanel control refreshes its content under the following conditions:
When another UpdatePanel control in the same page refreshes
When
any of the child controls originates a postback (for example, a button
click or a change of selection in a drop-down list with AutoPostBack=true)
When handling a postback event the page invokes the Update method on the UpdatePanel control
When the UpdatePanel control is nested inside another UpdatePanel control and the parent update panel is updated
When any of the trigger events for the UpdatePanel occur
You can control these conditions through a number of properties such as UpdateMode, ChildrenAsTriggers, and the collection Triggers.
To minimize the total number of postbacks and the amount of data being
roundtripped, you should pay a lot of attention to the values you
assign to these properties. Let’s delve deeper into this topic.
Configuring for Conditional Refresh
By
default, all updatable panels in a page are synchronized and refresh at
the same time. To make each panel refresh independently from the
others, you change the value of the UpdateMode property. The default value is Always,
meaning that the panel’s content is updated on every postback that
originates from anywhere in the page, from inside and outside the
updatable region.
By changing the value of the UpdateMode property to Conditional, you instruct the updatable panel to update its content only if it is explicitly ordered to refresh. This includes calling the Update method, intercepting a postback from a child control, or any of the events declared as triggers.
Normally, any control defined inside of an UpdatePanel control acts as an implicit trigger for the panel. You can stop all child controls from being triggers by setting the value of ChildrenAsTriggers to false. In this case, a button inside an updatable panel, if clicked, originates a regular full postback.
What if you want only a few controls within an UpdatePanel to act as triggers? You can define them as triggers of a particular UpdatePanel, or you can use the RegisterAsyncPostBackControl method on the ScriptManager class.
The RegisterAsyncPostBackControl
method enables you to register controls to perform an asynchronous
postback instead of a synchronous postback, which would update the
entire page. Here is an example of the RegisterAsyncPostBackControl method:
protected void Page_Load(object sender, EventArgs e)
{
ScriptManager1.RegisterAsyncPostBackControl(Button1);
}
The
control object you pass as an argument will be a control not included
in any updatable panels and not listed as a trigger. The effects of the
postback that originates from the control differ with regard to the
number of UpdatePanel controls in the page. If there’s only one UpdatePanel
in the page, the script manager can easily figure out which one to
update. The following code shows a page whose overall behavior may
change if one or two UpdatePanel controls are used.
protected void Button1_Click(object sender, EventArgs e)
{
// If there's only one UpdatePanel in the page, and it includes this Label control,
// the panel is refreshed automatically.
Label1.Text = "Last update at: " + DateTime.Now.ToLongTimeString();
// This Label control, not included in any UpdatePanel, doesn't have its UI
// refreshed. Its state, though, is correctly updated.
Label2.Text = "Last update at: " + DateTime.Now.ToLongTimeString();
}
When multiple panels exist, to trigger the update you have to explicitly invoke the Update method on the panel you want to refresh:
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = "Last update at: " + DateTime.Now.ToLongTimeString();
UpdatePanel1.Update();
}
All controls located inside of an UpdatePanel control are automatically passed as an argument to the RegisterAsyncPostBackControl method when ChildrenAsTriggers is true.
Note
A postback that originates from within an UpdatePanel control is often referred to as an asynchronous postback or an AJAX postback. Generally, these expressions are used to reference a postback conducted via a script taking advantage of XMLHttpRequest. |
Programmatic Updates
We have already mentioned the Update method quite a few times. It’s time we learn more about it, starting with its signature:
The
method doesn’t take any special action itself, but is limited to
requiring that the child controls defined in the content template of
the UpdatePanel control be refreshed. By using the Update
method, you can programmatically control when the page region is
updated in response to a standard postback event or perhaps during the
initialization of the page.
An invalid operation exception can be thrown from within the Update method in a couple of well-known situations. One situation is if you call the method when the UpdateMode property is set to Always.
The exception is thrown in this case because a method invocation
pre-figures a conditional update—you do it when you need it—which is
just the opposite of what the Always value of the UpdateMode property indicates. The other situation in which the exception is thrown is when the Update method is called during or after the page’s rendering stage.
So when should you get to use the Update method in your pages?
You resort to the method if you have some server logic to determine whether an UpdatePanel control should be updated as the side effect of an asynchronous postback—whether it is one that originated from another UpdatePanel in the page or a control registered as an asynchronous postback control.
Using Triggers
As mentioned, you can associate an UpdatePanel
control with a list of server-side events. Whenever a registered event
is triggered over a postback, the panel is updated. Triggers can be
defined either declaratively or programmatically. You add an event
trigger declaratively using the <Triggers> section of the UpdatePanel control:
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
<ContentTemplate>
...
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger
ControlID="DropDownList1"
EventName="SelectedIndexChanged" />
</Triggers>
</asp:UpdatePanel>
You
need to specify two pieces of information for each trigger—the ID of
the control to monitor, and the name of the event to catch. It is
essential to note that the AsyncPostBackTrigger component can catch only server-side events. Both ControlID and EventName
are string properties. For example, the panel described in the previous
code snippet is refreshed when any of the controls in the page posts
back (that is, its UpdateMode property defaults to Always) or when the selection changes on a drop-down list control named DropDownList1.
Note
Keep in mind that we’re talking about server-side events here. This implies that, in the previous example, the DropDownList1 control must have AutoPostBack equals to true in order to fire a postback. |
The event associated with the AsyncPostBackTrigger component triggers an asynchronous AJAX postback on the UpdatePanel
control. As a result, the host page remains intact except for the
contents of the referenced panel and its dependencies, if any. Usually,
the AsyncPostBackTrigger component points to controls placed outside the UpdatePanel. However, if the panel has the ChildrenAsTriggers property set to false,
it could make sense for you to define an embedded control as the
trigger. In both cases, when a control that is a naming container is
used as a trigger, all of its child controls that cause postback behave
as triggers.
Note
You can also add triggers programmatically by using the Triggers collection of the UpdatePanel control. The collection accepts instances of the AsyncPostBackTrigger class. |
Full Postbacks from Inside Updatable Panels
By default, all child controls of an UpdatePanel
that post back operate as implicit asynchronous postback triggers. You
can prevent all of them from triggering a panel update by setting ChildrenAsTriggers to false. Note that when ChildrenAsTriggers is false
postbacks coming from child controls are processed as asynchronous
postbacks and they modify the state of involved server controls, but
they don’t update the user interface of the panel.
There might be situations in which you need to perform full, regular postbacks from inside an UpdatePanel control in response to a control event. In this case, you use the PostBackTrigger component, as shown here:
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
<ContentTemplate>
...
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="DropDownList1"
EventName="SelectedIndexChanged" />
<asp:PostBackTrigger ControlID="Button1" />
</Triggers>
</asp:UpdatePanel>
The
preceding panel features both synchronous and asynchronous postback
triggers. The panel is updated when the user changes the selection on
the drop-down list; the whole host page is refreshed when the user
clicks the button.
A PostBackTrigger component causes referenced controls inside an UpdatePanel control to perform regular postbacks. These triggers must be children of the affected UpdatePanel.
The PostBackTrigger object doesn’t support the EventName
property. If a control with that name is causing the form submission,
the ASP.NET AJAX client script simply lets the request go as usual. The
ASP.NET runtime then figures out which server postback event has to be
raised for the postback control by looking at its implementation of IPostBackEventHandler.
Note
When should you use a PostBackTrigger
component to fire a full postback from inside an updatable panel? If
you need, say, a button to refresh a given panel, why not list the Click
event of the button as an asynchronous trigger and leave the button
outside the panel? Especially when complex and templated controls are
involved, it might not be easy to separate blocks of user interface in
distinct panels and single controls. So the easiest, and often the
only, solution is wrapping a whole block of user interface in an
updatable panel. If a single control in this panel needs to fire a full
postback, you need to use the PostBackTrigger component. |
Practical Steps for Adopting Updatable Panels
The UpdatePanel
control works with the idea of limiting the refresh of the page to only
the portions of it that are touched by the postback. A clear mapping
between user actions and portions of the page that are updated
consequently is key to successfully adopting the UpdatePanel control in an ASP.NET site.
The
first practical step for successfully migrating page behavior to
partial rendering entails that you, given the expected behavior of the
page, identify the portions of the page subject to refresh. If you
have, say, a complex table layout but only a small fragment of only one
cell changes in the page lifetime, there’s no reason to keep the whole
table in an UpdatePanel control. Only the server-side control that displays the modifiable text should be wrapped by the panel.
The portions of the page that you should consider to be candidates to be wrapped by an UpdatePanel control should be as small as possible. They also should include the minimum amount of markup and ASP.NET controls.
The
second step consists of associating each candidate region with a list
of refresh conditions. You basically answer the question, “When does this region get updated?” After you have compiled a list of candidate regions, and for each you have a list of refresh events, you’re pretty much done.
The final step is mapping this information to UpdatePanel
controls and triggers. If all the regions you have identified are
disjointed, you’re fine. If not, you use properties and triggers on the
UpdatePanel control to obtain the expected page behavior, thereby minimizing the impact of postbacks and page flickering.
If
needed, updatable panels can be nested. There’s no syntax limitation to
the levels of nesting allowed. Just consider that any nested panel
refreshes when its parent is refreshed regardless of the settings.
Let’s
be honest. It might not be a trivial task, and getting a disjoint set
of regions is not always possible. However, given the number of
properties supported by the UpdatePanel control, there’s always room for a good compromise between user experience and performance.
Giving Feedback to the User
A
partial rendering operation still requires a postback; it still uploads
and downloads the view state and fires the well-known page life cycle
on the server. The benefits of an asynchronous postback lie in the fact
that no full-page refresh is required and only a smaller amount of HTML
markup is returned to the client. An asynchronous postback might still
take a few seconds to complete if the server operation is a lengthy one.
The
mechanics of the asynchronous postback keeps the displayed page up and
running. So the biggest improvement of AJAX—the continuous feel with
the page—can become its major weakness if not handled properly. Having
the computer engaged in a potentially long task might be problematic.
Will the user resist the temptation of reclicking that button over and
over again? Will the user patiently wait for the results to show up?
Finally, will the user be frustrated and annoyed by waiting without any
clue of what’s going on? After all, if the page
is sustaining a full postback, the browser itself normally provides
some user feedback that this is happening. Using ASP.NET AJAX, the
callback doesn’t force a regular full postback and the browser’s visual
feedback system is not called upon to inform the user things are
happening.
In
the end, AJAX and partial rendering let developers arrange pages that
provide “continuous feel” to users and increased responsiveness. The
continuous experience, however, raises new issues. Feedback should be
given to users to let them know that an operation is taking place. In
addition, user-interface elements should be disabled if the user would
start new operations by clicking on the element.
ASP.NET AJAX supplies the UpdateProgress control to display a templated content while any of the panels in the page are being refreshed.
The UpdateProgress Control
The UpdateProgress control is designed to provide any sort of feedback on the browser while one or more UpdatePanel
controls are being updated. If you have multiple panels in the page,
you might want to find a convenient location in the page for the
progress control or, if possible, move it programmatically to the right
place with respect to the panel being updated. You can use cascading
style sheets (CSSs) to style and position the control at your leisure.
The user interface associated with an UpdateProgress control is displayed and hidden by the ASP.NET AJAX framework and doesn’t require you to do any work on your own. The UpdateProgress control features the properties listed in Table 2.
Table 2. Properties of the UpdateProgress Control
Property | Description |
---|
AssociatedUpdatePanelID | Gets and sets the ID of the UpdatePanel control that this control is associated with. |
DisplayAfter | Gets and sets the time in milliseconds after which the progress template is displayed. Set to 500 by default. |
DynamicLayout | Indicates whether the progress template is dynamically rendered in the page. Set to true by default. |
ProgressTemplate | Indicates the template displayed during an asynchronous postback that is taking longer than the time specified through the DisplayAfter property. |
An UpdateProgress control can be bound to a particular UpdatePanel control. You set the binding through the AssociatedUpdatePanelID
string property. If no updatable panel is specified, the progress
control is displayed for any panels in the page. The user interface of
the progress bar is inserted in the host page when the page is
rendered. However, it is initially hidden from view using the CSS display attribute.
When set to none, the CSS display
attribute doesn’t display a given HTML element and reuses its space in
the page so that other elements can be shifted up properly. When the
value of the display attribute is toggled on, existing elements are moved to make room for the new element.
If
you want to reserve the space for the progress control and leave it
blank when no update operation is taking place, you just set the DynamicLayout property to false.
Composing the Progress Screen
The ASP.NET AJAX framework displays the contents of the ProgressTemplate
property while waiting for a panel to update. You can specify the
template either declaratively or programmatically. In the latter case,
you assign the property any object that implements the ITemplate
interface. For the former situation, you can easily specify the
progress control’s markup declaratively, as shown in the following code:
<asp:UpdateProgress runat="server" ID="UpdateProgress1">
<ProgressTemplate>
...
</ProgressTemplate>
</asp:UpdateProgress>
You
can place any combination of controls in the progress template.
However, most of the time, you’ll probably just put some text there and
an animated GIF. (See Figure 1.)
Note that the UpdateProgress control is not designed to be a gauge component, but rather a user-defined panel that the ScriptManager control shows before the panel refresh begins and that it hides immediately after its completion.
Important
If you’re looking for a real gauge bar to monitor the progress of a server-side task, partial rendering and the UpdateProgress
control are not the right tools. As we’ll see later in the chapter,
polling is one of the main drawbacks of partial rendering and polling
is unavoidable for monitoring server tasks from the client. |
Client-Side Events for Richer Feedback
Each asynchronous postback is triggered on the client via script. The entire operation is conducted by the PageRequestManager client object, which invokes, under the hood, the XMLHttpRequest object. What kind of control do developers have on the underlying operation? If you manage XMLHttpRequest
directly, you have full control over the request and response. But when
these key steps are managed for you, there’s not much you can do unless
the request manager supports an eventing model.
The Sys.WebForms.PageRequestManager object provides a few events so that you can customize handling of the request and response. Table 3
lists the supported events that signal the main steps around an AJAX
postback that partially update a page. The events are listed in the
order in which they fire to the client page.
Table 3. Properties of the UpdateProgress Control
Event | Event Argument | Description |
---|
initializeRequest | InitializeRequestEventArgs | Occurs before the request is prepared for sending |
beginRequest | BeginRequestEventArgs | Occurs before the request is sent |
pageLoading | PageLoadingEventArgs | Occurs when the response has been acquired but before any content on the page is updated |
pageLoaded | PageLoadedEventArgs | Occurs after all content on the page is refreshed as a result of an asynchronous postback |
endRequest | EndRequestEventArgs | Occurs after an asynchronous postback is finished and control has been returned to the browser |
To register an event handler, you use the following JavaScript code:
var manager = Sys.WebForms.PageRequestManager.getInstance();
manager.add_beginRequest(OnBeginRequest);
The prototype of the event handler method—OnBeginRequest in this case—is shown here:
function beginRequest(sender, args)
The real type of the args
object, though, depends on the event data structure. By using any of
these events, you can control in more detail the steps of an
asynchronous request. Let’s dig out more.
The initializeRequest
event is the first in the client life cycle of an asynchronous request.
The life cycle begins at the moment in which a postback is made that is
captured by the UpdatePanel’s client-side infrastructure. You can use the initializeRequest event to evaluate the postback source and do any additional required work. The event data structure is the InitializeRequestEventArgs class. The class features three properties—postBackElement, request, and cancel.
The postBackElement property is read-only and evaluates to a DomElement object. It indicates the DOM element that is responsible for the postback. The request property (read-only) is an object of type Sys.Net.WebRequest and represents the ongoing request. Finally, cancel is a read-write Boolean property that can be used to abort the request before it is sent.
Immediately after calling the initializeRequest handler, if any, the PageRequestManager object aborts any pending async requests. Next, it proceeds with the beginRequest event and then sends the packet.
When the response arrives, the PageRequestManager
object first processes any returned data and separates hidden fields,
updatable panels and whatever pieces of information are returned from
the server. Once the response data is ready for processing, the PageRequestManager object fires the pageLoading
client event. The event is raised after the server response is received
but before any content on the page is updated. You can use this event
to provide a custom transition effect for updated content or to run any
clean-up code that prepares the panels for the next update. The event
data is packed in an instance of the class PageLoadingEventArgs. The class has three properties: panelsUpdating, panelsDeleting, and dataItems. The first two are arrays and list the updatable panels to be updated and deleted, respectively.
The pageLoaded
event is raised after all content on the page is refreshed. You can use
this event to provide a custom transition effect for updated content,
such as flashing or highlighting updated contents. The event data is
packed in the class PageLoadedEventArgs, which has three properties: panelsUpdated, panelsDeleted, and dataItems. The first two are arrays and list the updatable panels that were just updated and deleted, respectively.
The endRequest
event signals the termination of the asynchronous request. You receive
this event regardless of the success or failure of the asynchronous
postback.
Disabling Visual Elements During Updates
If
you want to prevent users from generating more input while a partial
page update is being processed, you can also consider disabling the
user interface—all or in part. To do so, you write handlers for beginRequest and endRequest events:
<script type="text/javascript">
function pageLoad()
{
var manager = Sys.WebForms.PageRequestManager.getInstance();
manager.add_beginRequest(OnBeginRequest);
manager.add_beginRequest(OnEndRequest);
}
</script>
You typically use the beginRequest event to modify the user interface as appropriate and notify the user that the postback is being processed:
// Globals
var currentPostBackElem;
function OnBeginRequest(sender, args)
{
// Get the reference to the button click (i.e., btnStartTask)
currentPostBackElem = args.get_postBackElement();
if (typeof(currentPostBackElem) === "undefined")
return;
if (currentPostBackElem.id.toLowerCase() === "btnStartTask")
{
// Disable the button
$get("btnStartTask").disabled = true;
}
}
The beginRequest handler receives event data through the BeginRequestEventArgs data structure—the args formal parameter. The class features only two properties—request and postBackElement. The properties have the same characteristics of analogous properties on the aforementioned InitializeRequestEventArgs class.
In the preceding code snippet, I disable the clicked button to prevent users from repeatedly clicking the same button.
At
the end of the request, any temporary modification to the user
interface must be removed. So animations must be stopped, altered
styles must be restored, and disabled controls must be re-enabled. The
ideal place for all these operations is the endRequest event. The event passes an EndRequestEventArgs object to handlers. The class has a few properties, as described in Table 4.
Table 4. Properties of the EndRequestEventArgs Control
Property | Description |
---|
dataItems | Returns
the client-side dictionary packed with server-defined data items for
the page or the control that handles this event. (More on registering
data items later.) |
Error | Returns an object of type Error that describes the error (if any) that occurred on the server during the request. |
errorHandled | Gets
and sets a Boolean value that indicates whether the error has been
completely handled by user code. If this property is set to true
in the event handler, no default error handling will be executed by the
ASP.NET AJAX client library.
|
Response | Returns an object of type Sys.Net.WebRequestExecutor that represents the executor of the current request. Most of the time, this object will be an instance of Sys.Net.XMLHttpExecutor. |
As you can see, when the endRequest
event occurs there’s no information around about the client element
that fired the postback. If you need to restore some user interface
settings from inside the endRequest event handler, you might need a global variable to track which element caused the postback:
function OnEndRequest(sender, args)
{
if (typeof(currentPostBackElem) === "undefined")
return;
if (currentPostBackElem.id.toLowerCase() === "btnStartTask")
{
$get("btnStartTask").disabled = false;
}
}
Wouldn’t
it be nice if you could visually notify users that a certain region of
the screen has been updated? As we’ve seen, partial rendering improves
the user experience with pages by eliminating a good number of full
refreshes. If you look at it from the perspective of the average user,
though, a partial page update doesn’t have a clear start and finish
like a regular Web roundtrip. The user doesn’t see the page redrawn and
might not notice changes in the user interface. A good pattern is
employing a little animation to show the user what has really changed
with the latest operation. You can code this by yourself using the pair
of beginRequest and endRequest events, or you can resort to a specialized component—an UpdatePanel extender control—as we’ll see in a moment.
Important
The disabled HTML attribute works only on INPUT elements. It has no effect on hyperlinks and <a> tags. If you plan to use LinkButtonfalse. Another effective trick might be to cover the area to be disabled with a partially opaque DIV.
controls, you have to resort to other JavaScript tricks to disable the
user interface. One possible trick is temporarily replacing the onclick
handler of the hyperlink with a return value of |
Aborting a Pending Update
A really user-friendly system always lets its users cancel a pending operation. How can you obtain this functionality with an UpdateProgress
control? The progress template is allowed to contain an abort button.
The script code injected in the page will monitor the button and stop
the ongoing asynchronous call if it’s clicked. To specify an abort
button, you add the following to the progress template:
<input type="button" onclick="abortTask()" value="Cancel" />
In the first place, the button has to be a client-side button. So you can express it either through the <input> element or the <button> element for the browsers that support this element. If you opt for the <input> element, the type attribute must be set to button. The script code you wire up to the onclick event is up to you, but it will contain at least the following instructions:
<script type="text/JavaScript">
function abortTask()
{
var manager = Sys.WebForms.PageRequestManager.getInstance();
if (manager.get_isInAsyncPostBack())
manager.abortPostBack();
}
</script>
You retrieve the instance of the client PageRequestManager object active in the client page and check whether an asynchronous postback is going on. If so, you call the abortPostBack method to stop it.
Important
Canceling
an ongoing update in this way is equivalent to closing the connection
with the server. No results will ever be received, and no updates will
ever occur on the page. However, canceling the update is a pure client
operation and has no effect over what’s happening on the server. If the
user started a destructive operation, the client-side Cancel button can just do nothing to stop that operation on the server. |
Light and Shade of Partial Rendering
Partial
rendering is definitely the easiest way to add AJAX capabilities to an
ASP.NET Web site. It has a relatively low impact on the structure of
existing pages, doesn’t require significant new skills, doesn’t require
exposure to JavaScript, and leaves the application model intact.
Advocates of a pure AJAX approach might say that there’s no AJAX at all
in partial rendering. And such a statement is not false, indeed.
Haven’t
we said that AJAX is key because it propounds a new programming
paradigm for building Web applications? And now we’re back to giving
kudos to partial rendering—an approach that admittedly maintains the old programming model of classic Web applications? What’s the point?
Overall,
partial rendering is only one possible way to approach AJAX. It
preserves most of your current investments and is relatively cheap to
implement. Partial rendering just adds AJAX capabilities to your pages.
It doesn’t constitute a true AJAX application. There’s no architectural
new point in partial rendering. It’s a great technique to quickly
update legacy applications, and it is an excellent choice when you lack
the time, skills, or budget to move on and redesign the application.
But in a good number of cases, an improved user interface and optimized
rendering is all that your users demand. So partial rendering would
perfectly fit in.
On
the other hand, building true AJAX applications where all the
presentation logic lives on the client written in JavaScript is not
trivial either, no matter how much help third-party libraries might
offer.
In the end,
you should be aware of the structural limitations that partial
rendering has. You might want to start with partial rendering to
improve your pages and then move on to other, more purely AJAX,
solutions to fix particular bottlenecks that still remain. My advice is
that a pure AJAX approach where a lot of JavaScript is involved is a
solution that should be considered carefully. And that you should have
good reasons for both adopting or refusing it.
Note
Why
is it so darned hard to write pure AJAX applications? AJAX applications
are all about the client, and the client is JavaScript and HTML. Both
have significant limitations in light of the complexity of applications
these days. JavaScript is an interpreted language, and it does not have
a particularly modern syntax. Additionally, JavaScript is subject to
the implementation that browsers provide. So a feature might be flaky
in one browser and super-optimized in another. Originally born as a
document format, HTML is used more as an application delivery format.
But for this purpose, HTML is simply inadequate because it lacks strong
and built-in graphics and layout capabilities. Silverlight 2.0 with its
embedded common language runtime (CLR), support for managed languages,
and full support for Windows Presentation Foundation (WPF) seems to
address both issues. |
Issues with Concurrent Calls
Partial
rendering has a number of positives, but it also has a couple of key
drawbacks. In particular, it doesn’t support concurrent asynchronous
postbacks. This means that you are not allowed to have two asynchronous
postbacks going on at the same time. Partial rendering bypasses the
standard browser’s mechanism that handles an HTTP request. It hooks up
the submit event of the form, cuts the standard browser handler out, and finally places the HTTP request using XMLHttpRequest.
The
request that reaches the Web server differs from a regular ASP.NET
request only for an extra HTTP header. The request sends in the
contents of the posting form, including the view-state hidden field.
The response is not pure HTML but represents a text record where each field describes the new status of a page element—update panels, hidden fields, scripts to run on loading.
As
you can see, the underlying model of partial rendering is still the
model of classic ASP.NET pages. It is a sort of stop-and-go model where
the users posts back, waits for a while, and then receives a new page.
While waiting for the next page, there’s not much the user can do. Only
one server operation per session occurs at a time. Partial rendering is
only a smarter way of implementing the old model.
From
a technical standpoint, the major factor that prevents multiple
asynchronous postbacks is the persistence of the view-state
information. When two requests go, both send out the same copy of the
view state, but each reasonably returns a different view state. Which
one has to be good for the page then?
Is
dropping the view state entirely an option, at least for asynchronous
postbacks? Whatever way you look at it, dropping the view state
increases the amount of JavaScript needed for each page. The view state
allows you to keep the server logic in C# or Visual Basic .NET and
generate the user interface through server controls. This is not to
say, though, that another approach isn’t possible. Anyway, partial
rendering works this way.
Whenever a request for an asynchronous postback is raised, the PageRequestManager
class checks whether another operation is pending. If so, by default,
it silently kills the ongoing request to make room for the new one—a last-win discipline.
This
fact has a clear impact on developers. In fact, you should always
modify the user interface to ensure that users can’t start a second
operation before the first is terminated. Otherwise, the first
operation is aborted in favor of the second. This happens in any case,
even when the two operations are logically unrelated.
Note
When
concurrent calls are necessary, you should consider moving that page
(if not the whole application) to a more AJAX-oriented design.
Alternatively, you can consider implementing that feature within the
page using some of the features covered in the next chapter, such as
page methods or script services. |
Issues with Polling
Among
other things, AJAX pages are popular because they can bring on the
client information in a timely manner. A page starts polling a remote
URL, grabs fresh information, and returns it to the client for the
actual display. Implemented via partial rendering, polling is subject
to being interrupted when the user starts a new partial rendering
operation to restart automatically at the end.
If this is not a problem for you, you can use the new Timer server control, as shown here:
<asp:Timer ID="Timer1" runat="server" Enabled="true" Interval="1000" ontick="Timer1_Tick" />
<asp:Button ID="Button1" runat="server" Text="Start task" onclick="Button1_Click" />
<asp:UpdateProgress ID="UpdateProgress1" runat="server" DynamicLayout="false">
<ProgressTemplate>
<img src="loading.gif" />
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Label ID="Label1" runat="server" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<hr />
<asp:UpdatePanel ID="UpdatePanel2" runat="server">
<ContentTemplate>
<asp:Label ID="lblClock" runat="server" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
</Triggers>
</asp:UpdatePanel>
The Timer control is the server counterpart of a client timer created using the window.setTimeout method. In the preceding code, the Timer control causes a postback every second as specified by the Interval property. The postback fires the Tick
event. By using the timer as the trigger of an updatable panel, you can
refresh the content of the panel periodically. In the code, the second UpdatePanel control just renders out a digital clock:
protected void Timer1_Tick(object sender, EventArgs e)
{
// Update the clock
lblClock.Text = DateTime.Now.ToString();
}
As in Figure 2, the clock stops working while the remote task triggered by the other button is still running.