16.1. Client CallbacksContrary to popular belief, ASP.NET 2.0 does include built-in support for Ajax. Well, at least in a limited way. Rather than calling it Ajax, however, Microsoft refers to this ASP.NET 2.0 technology as client callbacks. Client callbacks enable ASP.NET web applications to implement asynchronous calls to the server using JavaScript: one built-in JavaScript function requests data and another fetches and asynchronously processes it. You can think of client callbacks as a kind of lightweight postback, which is done using an XMLHttpRequest object. You can use JavaScript to display the data received from the server on the current page. All that is required for this task is to create a page that implements the ICallbackEventHandler interface. You can do this in code, or declaratively by including an @ Implements directive in the page. Then you must implement two methods defined for the interface:
The mediator between the client and the server is the Callback Manager. It creates the XMLHttpRequest object and also receives the result from the server. The communication between client and Callback Manager happens with the ICallbackEventHandler interface.
To demonstrate client callbacks, we'll port the familiar division example from Chapter 10 to Ajax. (Two numbers entered in an HTML form are divided using an Ajax call.) But before we delve into that example, we'll start with a simpler examplesquaring a number. To begin, we'll build the HTML form that a user will use to enter a number. Here's the markup: <form runat="server"> <div> <nobr> <input type="text" name="a" size="2" /> <sup>2</sup> = <span style="width: 50px;" /> </nobr> <br /> <input type="button" value="Square Number" runat="server" /> </div> </form> Note that the submit button is an ASP.NET HTML server control, because we want to access it in server code (unlike the other form elements, which we will change only with client script). The page must implement the ICallbackEventHandler interface, so we'll add an @ Implements directive to the ASP.NET page: <%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %> The ICallbackEventHandler interface consists of the RaiseCallbackEvent() and GetCallbackResult() methods, whose signatures are as follows: void ICallbackEventHandler.RaiseCallbackEvent(string arg) string ICallbackEventHandler.GetCallbackResult() In the RaiseCallbackEvent() method, you provide a parameter (the input for the server call), which has to be of type string. The GetCallbackResult() method performs the actual calculation on the server and returns the output, also as a string. The second method does not accept any parameters, so you need a member variable to save the input for later. In the RaiseCallbackEvent() method, you set this variable to the value of the argument sent from the client script: private string arg; void ICallbackEventHandler.RaiseCallbackEvent(string arg) { this.arg = arg; } The GetCallbackResult() method then has to perform the calculation. This requires some type conversions: HTML form data is always a string, but calculations require numeric values. The result, however, must be a string again: string ICallbackEventHandler.GetCallbackResult() { return Convert.ToString( Math.Pow( Convert.ToDouble(this.arg), 2)); } We are accepting input in RaiseCallbackEvent() and generating output in GetCallbackResult(), which concludes the work to be done on the server. In client script, we have to get the input (for example, from a text box filled in by the user). We must also accept the output returned by the server calculation to make the whole example work. Conveniently, ASP.NET can generate the JavaScript code that sends the input to RaiseCallbackEvent(), using the Page.ClientScript.GetCallbackEventReference() method. This method has several overloads, but the most functional one expects this set of parameters:
Usually, just the first four parameters suffice. In the particular example, the parameter to send is the value in the HTML input text box with the ID a. However, it is the HTML button that triggers the whole mechanism. To reference the contents of the text box, the following JavaScript expression is required: this.form.elements["a"].value The expression this.form links the current element (this) to the form in which it resides; from here on, the code can manipulate the text box in question. So here's the code you need to call GetCallbackEventReference(): string js = Page.ClientScript.GetCallbackEventReference( this, "this.form.elements[\"a\"].value", "callComplete", null); The return value of this function call is JavaScript code that starts the mechanism on the client. All you have to do is to make the HTML button execute this code whenever it is clicked. Here's most convenient way is to use ASP.NET to attach the JavaScript code to the button: submit.Attributes.Add("onclick", js);
All that is left to do is to implement the callback function, callComplete(). It accepts two parameters: the result (generated by GetCallbackResult()) and the context, if any (in our case, null). The resulting value is then written into the <span> element on the page. <script language="JavaScript" type="text/javascript"> function callComplete(result, context) { document.getElementById("aSquare").innerHTML = result; } </script> Example 16-1 shows the complete markup and code. Example 16-1. Using the ASP.NET 2.0 client callback function
Figure 16-1 shows the result displayed when a user enters a number and clicks the Square Number button of Example 16-1. Figure 16-1. Ajax with ASP.NET 2.0, but without Atlas![]() Looking at the source code in the browser, you can identify the code that was programmatically added to the submit button: <input name="submit" type="button" value="Square Number" onclick="WebForm_DoCallback('_ _Page',this.form.elements["a"] .value,callComplete,null,null,false)" /> The function WebForm_DoCallback() is loaded via the WebResource.axd virtual file and takes care of the XMLHttpRequest generation and server-side call (including a neat method of dynamically creating an invisible <iframe> element to exchange data with the server). Although it takes a series of steps, ASP.NET client callbacks work really well. However, you can only provide one parameter, and it has to be of type string. Admittedly, there are actually two parameters, if you take the context into account. To submit more parameters, or other data types or complex types, you have to serialize the data into a string so you can pass it using the single string parameter. The .NET Framework offers several serializers that can be useful here. However in some cases, a handmade serialization method might be simpler to implement. Going back to the division example, we can demonstrate this alternative approach of using nonstring values. In the division example, there are two numeric parameters that must be submitted to the server. The values come from the following HTML form: <form runat="server"> <div> <nobr> <input type="text" name="a" size="2" /> : <input type="text" name="b" size="2" /> = <span style="width: 50px;" /> </nobr> <br /> <input type="button" value="Divide Numbers" runat="server" /> </div> </form> To handle these two values, the main change happens in the GetCallbackEventReference() call. The JavaScript expression to access the values is different. This time, the script reads the values of both text boxes and concatenates them using a newline character as a delimiterthis is our simple handmade serialization format. Here's the code for this task: string js = Page.ClientScript.GetCallbackEventReference( this, "this.form.elements[\"a\"].value + \"\\n\" + this.form.elements[\"b\"].value", "callComplete", null); In server code, the GetCallbackResult() method must then split this delimited string into two values, convert them to numbers, divide them, and convert the result back into a string: string ICallbackEventHandler.GetCallbackResult() { char[] newline = {'\n'}; string[] values = this.arg.Split(newline); return Convert.ToString( (Convert.ToDouble(values[0]) / Convert.ToDouble(values[1]))); } The remaining changes are only cosmetic. Example 16-2 shows the code needed to implement this example. Of course you would want to add some proper error handling to the script (for example, to prevent a divide-by-zero error). For now, it is omitted for brevity and clarity reasons. Example 16-2. Using more than one parameter
Although this works, the shortcomings of this approach are obvious. You have to take care of the serialization of multiple or complex parameters yourself, either by implementing your own solution or by using the built-in serializers of the .NET Framework. Also, the client callback mechanism does not add any features like data binding or built-in interaction with ASP.NET controls. Therefore, although it is suitable for lighter "Ajax" tasks, it can be really cumbersome for more complex requirements. For a more sophisticated approach to client callbacks, you may be interested in the blog postings by Bertrand Le Roy on this subject. Christoph Wille gives a good overview at http://chrison.net/CallbacksInASPNET20.aspx and also provides links to Le Roy's blog entries.
|