One of the inherent limitations of web applications is the costly round-trip delay when a web page posts something back to the server and reloads the page. Instead of updating the required portion of a page, the entire page has to be refreshed when some elements of the page need to be changed. For example, if a user selects "US" as the country in a registration web page, a drop-down list should displays all the states in the U.S. Similarly, if he selects a country other than the U.S., that same list should change to the states of the selected country. Currently, implementation refreshes the entire page, which is often slow and causes frustration for the users. (Another technique is to send all of the states to the client and use JavaScript to display the related states when the user chooses a country, but this method largely inflates the size of the page and increases loading time.)
One technique that current ASP.NET 1.0/1.1 developers use to overcome this postback problem is to use the Microsoft XMLHTTP ActiveX object to send requests to server-side methods from client-side JavaScript. In ASP.NET 2.0, this process has been simplified and encapsulated within the function known as the Callback Manager.
The ASP.NET 2.0 Callback Manager uses XMLHTTP behind the scenes to encapsulate the complexities in sending data to and from the servers and clients. And so, in order for the Callback Manager to work, you need a web browser that supports XMLHTTP. Microsoft Internet Explorer is, obviously, one of them.
To illustrate how the Callback Manager in ASP.NET 2.0 work, I have created a
simple web application as shown in Figure 1. My example will allow a user to
enter a zip code into a text box and retrieve the city and state information
without posting back to the server. A TextBox control is used for entering a
zip code, while a Button HTML control is used to invoke the code (to get the
city and state based on the zip code) on the server. The result is then
displayed in the city and state textboxes.
I also have two DropDownList controls on the form. When a user selects a
particular country, the states (or cities) belonging to the selected country
would be retrieved from the server and displayed in the second DropDownList
control.

Figure 1. Populating the form with controls
First, the web form that is going to receive the postback needs to implement
the ICallbackEventHandler interface. I have also declared a public string (its
use will be evident later on):
Partial Class Default_aspx
Implements ICallbackEventHandler
Public callbackStr As String
This interface has only one method to implement -- the RaiseCallbackEvent
function. This function is invoked when the client sends a postback to the
server. In my case, this is the place to check the city and state information
of a zip code, as well as retrieve the states and cities of a country.
Ideally, all of this information should be retrieved from a web service, but for simplicity I have hardcoded the returning result.
Public Function RaiseCallbackEvent(ByVal eventArgument As String) As _
String Implements _
System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
If eventArgument.StartsWith("1:") Then
'---strips away the command
eventArgument = eventArgument.Substring(2)
'---get city and state based on Zipcode
Select Case eventArgument
Case "95472" : Return "Sebastopol,CA"
Case "02140" : Return "Cambridge,MA"
Case Else
Return "ZipCode not valid."
End Select
ElseIf eventArgument.StartsWith("2:") Then
'---strips away the command
eventArgument = eventArgument.Substring(2)
'---get states and cities related to country
Select Case eventArgument
Case "Sing" : Return "Singapore,"
Case "US" : Return _
"Alabama,California,Maryland,Massachusetts,New York,Oklahoma,Wisconsin,"
Case "UK" : Return _
"Birmingham,Cambridge,Christchurch,Leeds,Sheffield,"
Case Else
Return ""
End Select
Else
Return "Command not recognized"
End If
End Function
The eventArgument parameter is passed from the client. To retrieve state and
city based on zip code, the eventArgument parameter would look like this:
1:02140
Where 1: is the command and 02140 is the zip code.
To retrieve all states and cities based on country, the eventArgument parameter
would look like this:
2:US
Where 2: is the command and US is the country code.
Note that for the first command, the city and state are separated
by a comma; for example, Sebastopol,CA.
|
For the second command, the states (or cities) are also separated by commas;
for example, Alabama,California,Maryland,Massachusetts,New
York,Oklahoma,Wisconsin,.
Next, in the Page_Load event, you need to generate the code that performs the
call back using the GetCallbackEventReference method of the Page class:
Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
ddlCountry.Attributes.Add("onChange", "GetStatesFromServer()")
callbackStr = Page.GetCallbackEventReference(Me, "Command", _
"CallBackHandler", "context", "onError")
End Sub
Essentially, the callbackStr variable will store the following string:
WebForm_DoCallback('__Page',Command,CallBackHandler,context,onError)
What is important here is that Command is referring to the string that is
going to be passed to the server, while CallBackHandler means the function
that is invoked (on the client) when the server returns a result to the client.
Let's now define the functions on the client side. On the web form, switch to Source view and add in the following script block:
...
</head>
<body>
<script>
function GetStateFromZip(){
var Command = "1:" + document.forms[0].elements['txtZipCode'].value;
var context = new Object();
context.CommandName = "GetStateFromZip";
<%=callbackStr%>
}
function GetStatesFromServer() {
var Command = "2:" + document.forms[0].elements['ddlCountry'].value;
var context = new Object();
context.CommandName = "GetStatesFromCountry";
<%=callbackStr%>
}
function CallBackHandler(result, context) {
if (context.CommandName == "GetStateFromZip" ) {
var indexofComma = result.indexOf(",");
var City = result.substring(0,indexofComma);
var State = result.substring(indexofComma+1,result.length);
document.forms[0].elements['txtState'].value = State;
document.forms[0].elements['txtCity'].value = City;
} else
if (context.CommandName == "GetStatesFromCountry")
{
document.forms[0].elements['ddlState'].options.length=0;
while (result.length>0) {
var indexofComma = result.indexOf(",");
var State = result.substring(0,indexofComma);
result = result.substring(indexofComma+1)
opt = new Option(State,State);
document.forms[0].elements['ddlState'].add(opt);
}
}
}
function onError(message, context) {
alert("Exception :\n" + message);
}
</script>
<form id="form1" runat="server">
...
The GetStateFromZip and GetStatesFromServer functions basically formulate the
request to be sent to the server side; in this case, it takes the value of the
TextBox control (and DropDownList control) and puts it into the callbackStr.
The <%=callbackStr%> statement will insert the generated string into the
function, so that during runtime it becomes:
function GetStateFromZip(){
var Command = "1:" + document.forms[0].elements['txtZipCode'].value;
var context = new Object();
context.CommandName = "GetStateFromZip";
WebForm_DoCallback('__Page',Command,CallBackHandler,context,onError)
}
function GetStatesFromServer(){
var Command = "2:" + document.forms[0].elements['ddlCountry'].value;
var context = new Object();
context.CommandName = "GetStatesFromCountry";
WebForm_DoCallback('__Page',Command,CallBackHandler,context,onError)
}
Notice that both functions return the call to the CallBackHandler function --
the CallBackHandler function will be invoked when the server returns the result
to the client. Hence, there is a need to differentiate who the return caller is.
I use the context variable to set the command name for each type of call
(GetStateFromZip or GetStatesFromCountry).
The result will be returned as the variable result. The result is then parsed and displayed accordingly in the controls on the page.
To complete this example, remember to wire up the Button control with the
GetStateFromZip function.
<input id="Button1" type="button" value="Get City and State"
OnClick="GetStateFromZip()"
style="width: 144px; height: 24px"/>
<asp:DropDownList ID="ddlCountry" Runat="Server" >
<asp:ListItem>Select Country</asp:ListItem>
<asp:ListItem Value="US">United States</asp:ListItem>
<asp:ListItem Value="Sing">Singapore</asp:ListItem>
<asp:ListItem Value="UK">United Kingdom</asp:ListItem>
</asp:DropDownList>
<asp:DropDownList ID="ddlState" Runat="server">
</asp:DropDownList>
As for the Country DropDownList control, recall that earlier in the Page_Load
event we have this statement:
ddlCountry.Attributes.Add("onChange", "GetStatesFromServer()")
Essentially, this means that when the item in the DropDownList control changes,
the GetStatesFromServer function will be called.
Press F5 to test the application. You can now access the server without a
postback (see Figure 2).

Figure 2. Using the Callback Manager to avoid a postback
Note: JavaScript is case-sensitive, so be sure to use the correct case for control names.
The Callback Manager is a very useful feature in ASP.NET 2.0 that allows you to
build responsive web applications. However, notice that the RaiseCallbackEvent
function takes in and returns a result of string data type. Therefore, if you
have complex data types to transfer from the client to the server (and vice
versa), you need to serialize the complex object into a string and then back.
Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.
Return to ONDotnet.com
Copyright © 2009 O'Reilly Media, Inc.