1. Original Entry + Comments2. Write a Comment3. Preview Comment


July 01, 2004  |  Client Callback for ...  |  1231 hit(s)

... I was going to say "for Dummies," but that ain't right. Besides, it would probably be a copyright violation or something. Anyway, as Nikhil recently blogged, a new feature in Whidbey is support for client callbacks, a.k.a. asynch callbacks. One of the steady requests since ASP.NET 1.0 is some way to communicate between the client and the server without having to go through the whole postback cycle. In Whidbey, the page framework now has support that can do this via client script and XMLHTTP. The general idea is not new, as Nikhil points out. (When I mentioned this to my colleague Doug, his reaction was "What, they invented remote scripting yet again?") And people have been able to do essentially the same thing in 1.x (article here). The benefit is that it's now more-or-less cooked in.

More or less. Which is why we're here. In the initial specs I saw for client callback, and if you read Nikhil's entry, the primary application is intended to be custom controls. (Nikhil's example is a custom control, in fact.) The idea is that the custom control encapsulates the logic required to inject the necessary client script into the page (about which more in a moment) such that the page developer can just add the control and enjoy client callbacks.

But it's also possible to add client callbacks to a page even without creating a custom control. It requires a certain amount of client script that you have to add yourself, plus the all-important logic in the page to handle the callback. But it turns out to be not bad at all. I've been playing with this for work, and I've got what I think is a basic example. Let's see if I can make this clear enough. The way the page runs is roughly like this:
  1. The page renders to the browser like normal.
  2. The user make some gesture (or some other event occurs). In the handler for the event, the client script invokes a method that calls back to the page.
  3. The method makes a request back to the page on the server.
  4. The page on the server is recreated (like normal). But it only gets far enough in the process to be able to perform the logic you need.
  5. A method in server code is called. The method does whatever logic is required (database lookup, say) and returns a string.
  6. The response goes back to the page in the client (still sitting there).
  7. Another client script function catches the response and does something interesting with the results.
Important point to note: the page is still in the browser; there's no postback. All your current client context is still good (e.g. local client script variables).

The parts you have explicitly worry about are steps 2 (you write client script invoke the page), 5 (you write the server method), and 7 (you write the client script to catch the response). Let's start with the server side of things. As an example, I'll have a page where you can enter a ZIP code and get the server to return the corresponding city.

The class -- that is, the page -- that is the callback target has to implement the System.Web.UI.ICallbackEventHandler interface. The interface requires only one function, the RaiseCallbackEvent method. That's the method that will be called by client script. It can do anything you want, but it must return a string. Below is the server code for a Whidbey ASP.NET page (using code behind) that implements the required function. As an example, I'm faking a database lookup by maintaining my ZIP codes in a hash table. Note that I have only three entries, oh well.

The other part of the server code is that you have to dynamically generate the client script that performs the callback. I'm going to show the code and then explain it.

An aside: in Visual Web Developer, as soon as I added the Implements clause to the class declaration (which I could do with IntelliSense), the tool immediately created the skeleton for the method. Slick.

Partial Class CallBack_DB_aspx
Implements System.Web.UI.ICallbackEventHandler

Protected ZipTable As Hashtable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _
Handles Me.Load
ZipTable = New Hashtable()
ZipTable.Add("98106", "Seattle")
ZipTable.Add("10270", "New York")
ZipTable.Add("95110", "San Jose")

Dim cbReference As String = Page.GetCallbackEventReference(Me, _
"arg", "GetServerData", "context")
Dim callbackScript As String = ""
callbackScript &= "function CallTheServer(arg, context)"
callbackScript &= vbCrLf & "{"
callbackScript &= vbCrLf & " " & cbReference & ";"
callbackScript &= vbCrLf & "}"
Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
"CallTheServer", callbackScript, True)
End Sub

Public Function RaiseCallbackEvent(ByVal eventArgument As String) _
As String Implements _
System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
Dim city As String
If ZipTable(eventArgument) Is Nothing Then
city = "Not found"
Else
city = ZipTable(eventArgument).ToString()
End If
Return city
End Function
End Class
The RaiseCallbackEvent method and the fake database lookup are pretty clear, I hope. In addition, I'm dynamically creating a client script function named CallTheServer. The tricky bit (and the reason that this has to be done in server code) is that my script must include a call into a script library that actually does the work. The callback script is kind of like the __DoPostback function that performs the postback for server controls; as it happens, the callback function is named WebForm_DoCallback. We're technically not supposed to know that (or about __DoPostback either), and it's bad practice to call those types of generated library functions directly. So the proper way to build the call is to invoke GetCallbackEventReference, which returns a string representing the call, and then inject the script into the page using our good friend RegisterClientScriptBlock. (This is analogous to using GetPostBackClientEvent.) Note that one of the values passed to GetCallbackEventReference is "GetServerData". That's the name of the client script function that will be invoked on the callback.

Another aside: RegisterClientScriptBlock and its ilk are now in a new class represented here by Page.ClientScript. There have been improvements. One of them is that by passing true as the last parameter, you can have the function generate the surrounding <script></script> tags for you.

That's actually the hard part. On the client things are simple. Remember that the page injects a function named (in this case) CallTheServer. We need two additional functions. One of them calls CallTheServer, passing in any data you want to pass to the server. The second one is the function that gets the callback. The name of this function has to match the name you pass into GetCallbackEventReference. This is actually harder to explain than to show.

Here's the whole .aspx page. Note that there are no server controls on the page -- everything here is done exclusively with client script. This is a matched set with the .aspx.vb class file I showed earlier.

<%@ Page Language="VB" AutoEventWireup="false"
CompileWith="CallBack_DB.aspx.vb" ClassName="CallBack_DB_aspx" %>


<html>
<head runat="server">
<script type="text/javascript">
function CityLookup()
{
var zip = document.forms[0].zip.value;
CallTheServer(zip, "");
}


function GetServerData(city, context)
{
document.forms[0].city.value = city;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<input type="text" id="zip" />
<br />
<button onclick="CityLookup()">Look up city</button>
<br />
<input type="text" id="city" />
</div>
</form>
</body>
</html>
The second parameter in CallTheServer and GetServerData allows you to add extra information to the call that you might need. The information isn't used server side, but it is passed back to you in the callback. The idea is that you can add context, such as perhaps the ID of a control doing the call. I didn't need it, so I'm not using it here.

So that's the basics. It's actually not very difficult once you get the general structure sorted out. Hope you find it useful.