Introduction
One of the challenges that every ASP.NET 1.x web developer faced is that there isn't any native support for script callbacks to server without post-back of the current page. For example, retrieving and presenting the details of a selected employee in a dropdown list without forcing the full page refresh, and this is particularly an expensive call for UI-rich pages. So, how can we handle this?
Thanks to the ASP.NET team, they've built in this feature in ASP.NET 2.0, which allows calls to server events from client code without causing the page to post back and refresh. This is great, but we have to consider 2 facts: Time and Money. For time, we have to wait until ASP.NET 2.0 is released and we can't do anything in the meanwhile. For Money, we may be forced to upgrade Visual Studio .NET IDE from 2002/2003 to 2005 if we can't live without the Visual Studio.NET IDE. More importantly, implementing the script callbacks in ASP.NET 1.x isn't that hard at all. So, what are we waiting for?
Thanks to the extensible architecture of Page Controller framework, it gives developers the flexibility in hand to extend its functionalities, where native support is not sufficed. In this article, I'll walk you through the process of implementing the Script Callbacks Framework, and also provide a simplified but effective framework that you can use in ASP.NET 1.x.
Objectives
Several objectives have been set for the implementation of Script Callback Framework in ASP.NET 1.x, they are:
- Co-exists with the same functionality provided in ASP.NET 2.0 - avoids the use of similar function or variable name in our implementation.
- Provide a consistent programming model as ASP.NET - similar to the concept of
Page.GetPostBackEventReference
function.
- Emulate as many functionalities as Script Callbacks Framework implemented in ASP.NET 2.0.
- Provide synchronous and asynchronous callback.
Background
Script Callback Framework consists of 2 core components, which are PageTemplate.cs and ScriptCallback.js files. Each of them takes care of the callbacks processing at server side and client side, respectively. The steps involved in callbacks processing are shown below:
- Script preparation - Generate and attach the callback JavaScript code to Control's event.
- Script callbacks - Invoke the embedded JavaScript, which in turn opens the HTTP connection to the specified remote ASP.NET Page whenever the control's event is triggered.
- Server responses - Lookup and invoke the corresponding control to respond accordingly.
- Callback handling - Invoke the client Callback Handler or Error Handler based on the status code returned from the server's response.
Script preparation
PageTemplate
derives from System.Web.UI.Page
where it encapsulates most of the processing logic and plays a critical role in the Script Callback Framework. It provides the GetAsyncCallbackEventReference
and GetSyncCallbackEventReference
functions, which generate the JavaScript code to be attached to the Control's event.
public string GetAsyncCallbackEventReference(
System.Web.UI.Control control,
string args,
string cbHandler,
string context,
string errHandler)
public string GetSyncCallbackEventReference(
System.Web.UI.Control control,
string args,
string cbHandler,
string context,
string errHandler)
For example, the following call to GetAsyncCallbackEventReference
function, will generate the callback JavaScript code and attaches it to the IncreaseButton's onclick
event.
Server Side code
IncreaseButton.Attributes["onclick"] = GetAsyncCallbackEventReference
(
Form1,
String.Format("document.getElementById('{0}').value", txtValue.UniqueID),
"IncreaseValueHandler",
String.Format("document.getElementById('{0}')", txtValue.UniqueID),
null
);
Client Side code
<input name="IncreaseButton" id="IncreaseButton"
type="button" value="Increase Value"
onclick="javascript:WebForm_DoAsyncCallback('Form1',
document.getElementById('txtValue').value,
IncreaseValueHandler,
document.getElementById('txtValue'),
null);" />
PageTemplate
overrides the Render
method of System.Web.UI.Page
class to ensure that ScriptCallback.js file and all the script code necessary to perform the callback is correctly referenced within the page or control before rendering to the client. ScriptCallback.js file contains the client code to initialize the callback infrastructure, opens HTTP connection to specified remote ASP.NET Page for callback processing, as well as invokes the corresponding Callback Handler or Error Handler after receiving the result from the server.
Script callbacks
Whenever the control's event is fired, its embedded JavaScript code invokes the WebForm_DoAsyncCallback
or WebForm_DoSyncCallback
contained in ScriptCallback.js file to callback to the specified remote ASP.NET Page for processing. Internally, it uses a COM object to issue an HTTP POST or GET command to the specified target URL. This COM object comes with Internet Explorer v5.0 or above, and it is an old acquaintance of many developers:
var xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
The HTTP verb is GET or POST depending on the size of the data to be sent. If the size exceeds 2KB, a POST command is used. The HTTP request consists of three logical elements: __SCRIPTCALLBACKID
, __SCRIPTCALLBACKPARAM
, and posted data. The __SCRIPTCALLBACKID
value contains the ID of the control to be invoked (the event target parameter), whereas __SCRIPTCALLBACKPARAM
carries the input parameter for the server-side stub method.
Server responses
PageTemplate
overrides the OnInit
method of System.Web.UI.Page
class to determine if the current request is Postback or Callback mode. It looks for a __SCRIPTCALLBACKID
entry in the Request
collection. If so, it sets the public IsCallback
property to true and concludes that a callback invocation is being made. In the OnLoad
method, it first calls the base.OnLoad
method to ensure all the controls including those dynamically created controls are properly initialized. If it's a callback, it then invokes the HandleClientCallback
function to further process the request.
Internally, it uses the value obtained from __SCRIPTCALLBACKID
entry, which is the ID of the control to be invoked. It looks for the specified control in the Page
's Controls
collection and checks to see if the referenced control implements the IClientCallbackEventHandler
interface (which could be the page itself). If the Control
implements the required interface, the PageTemplate
invokes the RaiseClientCallbackEvent
method on the interface, and prepares the response from the results of the call.
The IClientCallbackEventHandler
interface has just one method with the following signature:
string RaiseClientCallbackEvent(string eventArgument);
The eventArgument
parameter for the method is retrieved from the Request
collection of posted values. The string representation of the input data is contained in an entry named __SCRIPTCALLBACKPARAM
. This string can be anything you want and need, including numbers, dates, comma-separated values, XML data or Base64 data, JavaScript and so forth.
PageTemplate
uses different status codes to notify client script code about the status of callback processing at server. The status code used here has different meaning than those used in the standard HTTP STATUS_CODE
. The table shown below lists down the 4 possible status codes and their meaning:
STATUS |
DESCRIPTION |
200 |
OK |
404 |
Unable to find the specified control |
500 |
Internal Error (Unknown Error) |
501 |
The specified control does not implement the IClientCallbackEventHandler interface |
Note: The status code is appended into a custom HTTP Header entry named "__SCRIPTCALLBACKSTATUS
".
Callback handling
Once a callback operation has completed, the client script will be notified to check the status code returned from the custom HTTP Header entry named "__SCRIPTCALLBACKSTATUS
". It invokes the registered Callback Handler if the status code is "200". Otherwise, the registered Error Handler is invoked to complete the job.
Using the code
With Script Callback Framework in place, developing a Page that makes use of script callbacks is as easy as the few steps listed below:
- Implement the
IClientCallbackEventHandler
interface on any control, which intends to support the callback. The control can be Page
, UserControl
as well as any Server Controls.
- Write the server-side code in the
RaiseClientCallbackEvent
method that will be invoked from the client. In doing so, you need to define the data model for the call and decide what information to be exchanged and in what format. The actual data being exchanged must be a string, but the contents of the string can be anything you want and need, including numbers, dates, comma-separated values, XML data or Base64 data, JavaScript and so forth.
- Assign a unique ID to every control that supports
IClientCallbackEventHandler
interface either declaratively (HTML) or programmatically (Code).
- Attach the JavaScript code emitted from
GetAsyncCallbackEventReference
or GetSyncCallbackEventReference
function to the control's event. When invoked, initiate and handle the callbacks operation asynchronously or synchronously. Please look at the Limitation section if System.Web.UI.Page
or its derivative is used as the callback target.
Limitation
System.Web.UI.Page
or its derivative cannot be used as the first input parameter for GetAsyncCallbackEventReference
or GetSyncCallbackEventReference
functions because the system is unable to find out their ID. The workaround is that you should declare a variable of type System.Web.UI.HtmlControls.HtmlForm
, and its name must match the Form
element's ID
used in the Page's HTML source, and use this variable as the first input parameter to the aforementioned functions.
HTML
<form id="Form1" method="post" runat="server"></form>
Code behind
protected System.Web.UI.HtmlControls.HtmlForm Form1;
IncreaseButton.Attributes["onclick"] = GetSyncCallbackEventReference
(
Form1,
String.Format("document.getElementById('{0}').value",
txtValue.UniqueID), "IncreaseValueHandler",
String.Format("document.getElementById('{0}')",
txtValue.UniqueID), null
);
- Write the Callback Handler or Error Handler JavaScript function in the Page's HTML source or in any script file referenced by the Page.
In short, you can use callbacks to update individual elements of a page, such as a Label
or a Panel
, provide different views of the same data, download additional information on demand, or auto-fill one or more fields. To merge the server-side generated values with the existing page, you typically use the Page's DHTML object model. You give each updateable HTML tag a unique ID and modify its contents using the innerHTML
property or any other property and method the DOM supplies.
Last, but not least, I've included a sample, available from the link at the top of this article, that demonstrates the capability of Script Callback Framework.
Points of Interest
Throughout the numerous testing on Script Callback Framework, I found that Microsoft.XMLHTTP
COM object caches the result of each call by its requested URL and post data. No connection will be opened to the requested URL if the same requested URL and post data have been used before. However, the numbers of cache items is unknown to me.
Conclusion
Script callbacks allow you to perform callbacks to the server without refreshing the whole page, which gives users the illusion that everything is taking place on the client. Once again, I have presented the Script Callback Framework which is a simplified version of code that will be completely built when ASP.NET 2.0 is released. You might as well get the functionality you want today, and you'll be ahead of the curve in understanding this similar concept in ASP.NET 2.0 tomorrow.