Client Callbacks are probably the less known (and I dare say, less loved) of all the AJAX options in ASP.NET, which also include the UpdatePanel, Page Methods and Web Services. The reason for that, I believe, is it’s relative complexity: Get a reference to a JavaScript function; Dynamically register function that calls the above reference; Have a JavaScript handler call the registered function. However, it has some the nice advantage of being self-contained, that is, doesn’t need additional files, such as web services, JavaScript libraries, etc, or static methods declared on a page, or any kind of attributes. So, here’s what I want to do: Have a DOM element which exposes a method that is executed server side, passing it a string and returning a string; Have a server-side event that handles the client-side call; Have two client-side user-supplied callback functions for handling the success and error results. I’m going to develop a custom control without user interface that does the registration of the client JavaScript method as well as a server-side event that can be hooked by some handler on a page. My markup will look like this: 1: <script type="text/javascript"> 1: 2: 3: function onCallbackSuccess(result, context) 4: { 5: } 6: 7: function onCallbackError(error, context) 8: { 9: } 10: </script>
2: <my:CallbackControl runat="server" ID="callback" SendAllData="true" OnCallback="OnCallback"/>
The control itself looks like this:
1: public class CallbackControl : Control, ICallbackEventHandler
2: {
3: #region Public constructor
4: public CallbackControl()
5: {
6: this.SendAllData = false;
7: this.Async = true;
8: }
9: #endregion
10:
11: #region Public properties and events
12: public event EventHandler<CallbackEventArgs> Callback;
13:
14: [DefaultValue(true)]
15: public Boolean Async
16: {
17: get;
18: set;
19: }
20:
21: [DefaultValue(false)]
22: public Boolean SendAllData
23: {
24: get;
25: set;
26: }
27:
28: #endregion
29:
30: #region Protected override methods
31:
32: protected override void Render(HtmlTextWriter writer)
33: {
34: writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
35: writer.RenderBeginTag(HtmlTextWriterTag.Span);
36:
37: base.Render(writer);
38:
39: writer.RenderEndTag();
40: }
41:
42: protected override void OnInit(EventArgs e)
43: {
44: String reference = this.Page.ClientScript.GetCallbackEventReference(this, "arg", "onCallbackSuccess", "context", "onCallbackError", this.Async);
45: String script = String.Concat("\ndocument.getElementById('", this.ClientID, "').callback = function(arg, context, onCallbackSuccess, onCallbackError){", ((this.SendAllData == true) ? "__theFormPostCollection.length = 0; __theFormPostData = ''; WebForm_InitCallback(); " : String.Empty), reference, ";};\n");
46:
47: this.Page.ClientScript.RegisterStartupScript(this.GetType(), String.Concat("callback", this.ClientID), script, true);
48:
49: base.OnInit(e);
50: }
51:
52: #endregion
53:
54: #region Protected virtual methods
55: protected virtual void OnCallback(CallbackEventArgs args)
56: {
57: EventHandler<CallbackEventArgs> handler = this.Callback;
58:
59: if (handler != null)
60: {
61: handler(this, args);
62: }
63: }
64:
65: #endregion
66:
67: #region ICallbackEventHandler Members
68:
69: String ICallbackEventHandler.GetCallbackResult()
70: {
71: CallbackEventArgs args = new CallbackEventArgs(this.Context.Items["Data"] as String);
72:
73: this.OnCallback(args);
74:
75: return (args.Result);
76: }
77:
78: void ICallbackEventHandler.RaiseCallbackEvent(String eventArgument)
79: {
80: this.Context.Items["Data"] = eventArgument;
81: }
82:
83: #endregion
84: }
And the event argument class:
1: [Serializable]
2: public class CallbackEventArgs : EventArgs
3: {
4: public CallbackEventArgs(String argument)
5: {
6: this.Argument = argument;
7: this.Result = String.Empty;
8: }
9:
10: public String Argument
11: {
12: get;
13: private set;
14: }
15:
16: public String Result
17: {
18: get;
19: set;
20: }
21: }
You will notice two properties on the CallbackControl:
Async: indicates if the call should be made asynchronously or synchronously (the default);
SendAllData: indicates if the callback call will include the view and control state of all of the controls on the page, so that, on the server side, they will have their properties set when the Callback event is fired.
The CallbackEventArgs class exposes two properties:
Argument: the read-only argument passed to the client-side function;
Result: the result to return to the client-side callback function, set from the Callback event handler.
An example of an handler for the Callback event would be:
1: protected void OnCallback(Object sender, CallbackEventArgs e)
2: {
3: e.Result = String.Join(String.Empty, e.Argument.Reverse());
4: }
Finally, in order to fire the Callback event from the client, you only need this:
1: <input type="text" id="input"/>
2: <input type="button" value="Get Result" onclick="document.getElementById('callback').callback(callback(document.getElementById('input').value, 'context', onCallbackSuccess, onCallbackError))"/>
The syntax of the callback function is:
arg: some string argument;
context: some context that will be passed to the callback functions (success or failure);
callbackSuccessFunction: some function that will be called when the callback succeeds;
callbackFailureFunction: some function that will be called if the callback fails for some reason.
Give it a try and see if it helps!