Change is always a central issue for software development. In this regard Microsoft AJAX Web application is never an exception. You may find your application packed with a plethora of UpdatePanels and event handlers. Besides, you may be confused that ASP.NET AJAX offers more than one technique to do one thing but you cannot use them
consistently. As a result, it becomes hard to make changes in your application.
Lacking guidelines and best practices, the inadvertent and ad hoc use of ASP.NET AJAX technology inevitably ends up with an application where it is hard to make changes. In this article I will present a systematic approach, or some best practices, to make the UpdatePanel-based ASP.NET AJAX applications loosely coupled, simplified, consistent, and straightforward and thus easy to change. The new approach proves easy to learn and use and still retains the most advantages of ASP.NET AJAX.
UpdatePanel Fundamentals and Problems
The AJAX capabilities of ASP.NET AJAX Framework 1.0 mainly come out of two great server controls ScriptManager and UpdatePanel. The non-visual component ScriptManager is responsible for the overall AJAX plumbing. UpdatePanel gives developers direct control on which part needs to be partially updated and what element can trigger asynchronous postbacks. Looking at a simple AJAX application (Listing 1), the use of ScriptManager and UpdatePanel is very easy and straightforward. An UpdatePanel consists of two sections: ContentTemplate and Triggers. All the content in the ContentTemplate section is able to get updated asynchronously. The Triggers section allows you to register the triggers which will update the content of the UpdatePanel asynchronously. UpdatePanel has one Boolean property called ChildrenAsTriggers. When it is turned on, all triggers in the content section automatically become asynchronous triggers of that UpdatePanel. For other information please see MSDN documentation.
There are a few issues you need to be clear about when you use UpdatePanel in your ASP.NET AJAX applications:
Determine the area in the Web Form that needs to be updated asynchronously. This is very simple: just wrap that area with an UpdatePanel control.
Specify the asynchronous triggers. You can declaratively register the trigger in the Triggers section.
Asynchronous postback event handling. You can handle the asynchronous postback events the same way as the normal postback.
Some controls like Repeater, GridView, and UserControl can also act as asynchronous triggers for the UpdatePanel with their specific or custom events such as ItemCommandEvent.
In addition to the declarative approach, ASP.NET AJAX also allows you to register asynchronous triggers and update multiple UpdatePanels programmatically.
You may cheer for such a powerful yet simple AJAX framework. However, things in the real world turn out to be not that simple. If you have a Web Form with a dozen UpdatePanels (some possibly nested) and plenty of asynchronous triggers, you will have a totally different view of the situation: you will find that the advantages above may turn into headaches if you’ve used these techniques in a casual way. Your application ends up being hard to maintain and change. The following list gives you the problematic issues around UpdatePanel-based AJAX applications within large-scale AJAX applications:
- The declarative approach may not work all the time; for example, you cannot register a nested trigger if it has no instance for reference. You must resort to a programmatic approach as a remedy. However, you will find that you cannot use either approach in a consistent manner.
- You can use a trigger to update more than one UpdatePanel; an UpdatePanel can get updated via more than one trigger. The declarative approach cannot give you a centralized view of how UpdatePanel and triggers are related.
- Event handling is a clean and quick way to handle asynchronous postback events. But this may not work well for Web Forms containing many asynchronous triggers with their corresponding dozen separate event handlers.
- The use of complex triggers as asynchronous triggers, like Repeater, GridView, and UserControl, makes the code logic complicated and difficult to understand.
Lacking guidelines and best practices, the inadvertent and ad hoc use of ASP.NET AJAX technology inevitably ends up with an application where it is hard to make changes.
Is there a best practice to get around the issues above? The answer is yes. In the rest of this article I will outline the new approach to systematically resolving these issues. First of all, I will show how to loosely couple UpdatePanels and asynchronous triggers at design time. Next, I will show you how to specify UpdatePanels and asynchronous triggers in a consistent manner, avoid complex triggers, and minimize the number of UpdatePanels. Last, I will illustrate how to link asynchronous triggers with related UpdatePanels and how to centralize the handling of the asynchronous postback events.
Decouple Asynchronous Triggers and UpdatePanels at Design Time
Decoupling asynchronous triggers and UpdatePanels at design time is a preliminary step to consistently specifying UpdatePanels and asynchronous triggers and centralizing the handling of asynchronous postback events. Linking asynchronous triggers with their related UpdatePanels still happens, but programmatically in the code. Decoupling enables you to specify the UpdatePanels and asynchronous triggers independently of each other.
It turns out that decoupling is very easy. First of all, turn off the Boolean property ChildrenAsTriggers for all UpdatePanels. This means that asynchronous triggers in the ContentTemplate section of the UpdatePanel will not update a nested UpdatePanel. Second, don’t register triggers in the Triggers section of the UpdatePanel (there is only one exception, which I will discuss in the next section on avoiding unnecessary UpdatePanels). Next, you will learn a couple of simple ways to make asynchronous triggers.
Designate Asynchronous Triggers Consistently
In this approach you designate all asynchronous triggers consistently and declaratively at design time. In addition, you deliberately avoid complex triggers and unnecessary UpdatePanels.
Rule of Thumb-Use UpdatePanel
By design, any triggers residing at the ContentTemplate section automatically become asynchronous triggers, for example, Button, LinkButton, etc. This works for nested UpdatePanels, UserControls within an UpdatePanel, and iterative controls such as Repeater or GridView within an UpdatePanel. For example, if you want to make a Button in the ItemTemplate of a Repeater an asynchronous trigger, you can wrap the whole Repeater into an UpdatePanel; or you can add UpdatePanel in the ItemTemplate. The latter allows you to fine tune the control instead of making asynchronous triggers since the former method transforms any other triggers into asynchronous triggers as a side effect. Remember to turn off ChildrenAsTriggers for all the UpdatePanels.
Avoid Unnecessary UpdatePanels
The overuse of UpdatePanel to specify asynchronous triggers may result in too many trivial UpdatePanels in the page. This could be an issue for maintenance. To mitigate this problem you can create a dummy UpdatePanel in the page without any content and register all page-level asynchronous triggers in its Triggers section:
<asp:UpdatePanel ID="upTrigger" runat="server"
UpdateMode="Conditional" RenderMode="Inline"
ChildrenAsTriggers="false">
<Triggers>
<asp:AsyncPostBackTrigger
ControlID ="ddlStartYear"
EventName ="SelectedIndexChanged" />
<asp:AsyncPostBackTrigger
ControlID ="btnSearch" EventName ="Click" />
<asp:AsyncPostBackTrigger
ControlID ="btnSave" EventName ="Click" />
<asp:AsyncPostBackTrigger
ControlID ="btnSort" EventName ="Click" />
………
</Triggers>
</asp:UpdatePanel>
This is the only place to register asynchronous triggers in the Triggers section of the UpdatePanel. By doing so, you can greatly reduce the number of UpdatePanels.
Remember to turn off the Boolean property ChildrenAsTriggers for all UpdatePanels.
Avoid Complex Triggers and Custom Events
ScriptManager has a useful property called AsynchronousPostbackElementID, the client ID of the asynchronous trigger, which reveals which element in the page produces the asynchronous postback event. You will find that even if you use complex triggers as asynchronous triggers AsynchronousPostbackElementID still represents the client ID of the simple controls such as Button, LinkButton, DropDownList, RadioButton, RadioButtonList, etc, which actually produces the asynchronous postbacks. To make code easier to understand, my approach avoids using controls such as asynchronous triggers. In the next section you will see some samples of AsynchronousPostbackElementID.
Centralize the Handling of Asynchronous Postback Events
In the book, AJAX in Action (Manning Publications, 2005), the chapter titled “The Page as an Application” vividly describes the ASP.NET AJAX Web application in that one Web Form typically encompasses lots of functionalities. In such cases, the centralized case or if-else statement proves more succinct and efficient than the scattered normal event handlers to process the asynchronous postback events. As discussed above, AsynchronousPostBackElementID contains all the information needed to identify the element in the Web Form triggering the asynchronous postback.
AsynchronousPostBackElementID looks like the following:
btnInOuterPanel //button
chkTest //check box
rbTest //radio button
rblTest$0 //radio button list
btnInInnerPanel //button inside UpdatePanel
//trigger button in the ItemTemplate of a Repeater
//Repeater is registered as the async trigger
rptComplextTrigger$ctl00$btnInComplextTrigger
//button in the ItemTemplate of a Repeater
//which is in an UpdatePanel
rptWithUpTriggerWrapper$ctl01$btnTest
//radio button list is in an UpdatePanel which is
// in the ItemTemplate of a Repeater
rptUpTriggerWrapperIndivisually$ctl00$rblTest2$2
//button inside the UserControl
WebUserControl1$btnUserControl
Mostly AsynchronousPostBackElementID is the same as the ID of that control if it is at the page level. When the asynchronous trigger is nested in other controls, for example, rptWithUpTriggerWrapper$ctl01$btnTest, you need to traverse the control tree to locate the instance of the asynchronous trigger. AsynchronousPostBackElementID may end with the ID of the asynchronous trigger, but for list controls such as RadioButtonList, CheckBoxList, and so on, AsynchronousPostBackElementID ends with an extra sequence number, for example, rptUpTriggerWrapperIndivisually$ctl00$rblTest2$2.
You can also apply this method to the normal postback events, where having too many separate event handlers is a problem. However, you need to identify which element is producing the postback in the first place.
You can use simple string matching to identify the triggers. For example, the TriggerIs method below gives you an example of checking the leading control ID and the tailing control ID to identify the asynchronous trigger. The Boolean parameter skipTailingSequenceNo indicates whether the function will remove the tailing sequence number as required for list controls (DropDownList control is an exception):
public static bool TriggerIs(string
trggierClientId, Control startControl, string
triggerId, bool skipTailingSequenceNo)
{
string trimTriggerClientId = trggierClientId;
if (skipTailingSequenceNo)
{
int pos =
trggierClientId.LastIndexOf("$");
if (pos >= 0)
{
trimTriggerClientId =
trggierClientId.Substring(0, pos);
}
}
return
trimTriggerClientId.StartsWith
(startControl.ClientID) &&
trimTriggerClientId.EndsWith(triggerId);
}
Now the centralized processing logic for all the asynchronous postback events becomes very straightforward (Listing 2).
In Listing 2, the overridable method OnPreRenderComplete is a good location to host all the logic. You can also apply this method to the normal postback events, where having too many separate event handlers is a problem. However, you need to identify which element is producing the postback in the first place.
Link Asynchronous Triggers and UpdatePanels Programmatically
The final step is to link asynchronous triggers with their related UpdatePanels, as shown in the centralized processing logic above. If the related UpdatePanel has an instance at the page level, you can directly reference that instance. If the UpdatePanel is nested in other controls without a page-level instance you need to traverse the control tree to obtain the instance of the asynchronous trigger and its related UpdatePanel, and then reference them. Searching the Internet will give you a ton of examples on how to traverse the control tree of the Web Form. In addition, AsynchronousPostBackElementID also provides the hierarchical information on the instance of the asynchronous trigger. The function LookupAsyncElement illustrates how to find out the instance of the asynchronous postback element inside a complex control (Listing 3).
Searching the Internet will give you a ton of examples on how to traverse the control tree of the Web Form.
For example, to get the containing UpdatePanel of a RadioButtonList inside a Repeater control, you just call asyncTrigger.Parent.Parent since the direct Parent refers to the ContentTemplate (Listing 4).
Wrapping Up
So now that I’ve finished outlining this approach, you may realize how it can simplify the issues around making changes to a complex AJAX application. This approach is a selective and restrictive use of existing ASP.NET AJAX techniques, but it makes an AJAX application loosely coupled, simplified, consistent, and straightforward. It may not be very advantageous for a small AJAX application with one or two asynchronous triggers, but it will save you trouble and headaches in large-scale AJAX applications. For these applications, you should now understand what to change and where to make changes. I developed this approach throughout the development of two large-scale AJAX applications. For the first one I used the techniques from books and the Internet, but the application ended up being messed up and hard to maintain and change. The side benefit is that it inspired me to create this approach. So in the second application I applied this approach and the application ended up being very successful. Since the application’s inception, there have been a lot of changes; all changes were done with ease. I hope you will enjoy this approach.