Introduction
Since ASP.NET 2.0, the .NET framework ships a feature called client callback. These callbacks are much lighter than the partial postbacks provided by the ASP.NET AJAX Extensions' UpdatePanel
. Client callbacks send a specified string to the server, and retrieve only a server side generated string as result rather than a re-rendered partial page, as in the case of the UpdatePanel
.
Nasir Ali Khan has done an exellent job in his article on CodeProject about client callbacks.
This article consists of the following three parts:
- Multiple client callback implementation.
- Simple ASP.NET page example.
- Custom composite control example.
Prerequisite
The reader of this article needs to be familiar with the concept of client callbacks being provided as part of the .NET framework since version 2.0 .
Part I: Multiple client callback implementation
The implementation of the ICallbackEventHandler
interface restricts a page/control to a single callback method declaration (RaiseCallbackEvent(string eventArgument)
).
What others suggested:
In order to distinguish callbacks in the RaiseCallbackEvent
method, prefixing the eventArgument
string was suggested in other articles. The author's opinion is that this is an unaesthetic and error prone approach.
Smarter approach:
A custom control shall serve as a "shell" for the sole purpose of hosting the client callback functionality:
using System.Web.UI;
[assembly: TagPrefix("HelveticSolutions.Web.UI.WebControls", "HelveticSolutions")]
namespace HelveticSolutions.Web.UI.WebControls
{
[ToolboxData("<{0}:ClientCallback runat="server"></{0}:ClientCallback>")]
public class ClientCallback : Control, ICallbackEventHandler
{
#region event/delegate declaration
public delegate string OnRaiseCallbackEvent(string eventArgument);
public event OnRaiseCallbackEvent Raise;
#endregion
#region members
private string callbackResult = string.Empty;
#endregion
public void RaiseCallbackEvent(string eventArgument)
{
if (Raise != null) callbackResult = Raise(eventArgument);
}
public string GetCallbackResult()
{
return callbackResult;
}
}
}
The control implements the ICallbackEventHandler
interface, and exposes the callback method as a public event (Raise
).
By adding as many ClientCallback
controls to a page or user/custom control as the required callbacks, the implementation for each of them can by nicely kept apart.
Part II: Simple ASP.NET page example
In the Visual Studio Designer, the ClientCallback
control can be easily placed onto the ASP.NET page by drag and drop. The following code example contains two ClientCallback
controls, and two Button
s that initiate the callbacks. For the simplicity of this example, both Button
s use the same callback complete function. However, in a real world implementation, you would implement a function for each of them in order to handle the result accordingly.
<%@ Page Language="C#" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="TestWeb._Default" %>
<%@ Register Assembly="HelveticSolutions.Web.UI.WebControls"
Namespace="HelveticSolutions.Web.UI.WebControls" TagPrefix="SmartSoft" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>HelveticSolutions - Multiple client callbacks example</title>
<script type="text/javascript">
function button1Clicked(arg, context, callback) {
<%= Button1CallbackReference %>;
}
function button2Clicked(arg, context, callback) {
<%= Button2CallbackReference %>;
}
function callbackComplete(result, context) {
document.getElementById("result").innerHTML = result;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<input type="button" onclick="button1Clicked('', '',
callbackComplete);" value="Button 1" />
<input type="button" onclick="button2Clicked('', '',
callbackComplete);" value="Button 2" />
<div id="result"></div>
</div>
<SmartSoft:ClientCallback ID="Button1Callback"
runat="server"></SmartSoft:ClientCallback>
<SmartSoft:ClientCallback ID="Button2Callback"
runat="server"></SmartSoft:ClientCallback>
</form>
</body>
</html>
Code-behind:
using System;
using System.Web.UI;
namespace TestWeb
{
public partial class _Default : Page
{
#region properties
protected string Button1CallbackReference
{ get { return GetCallbackReference(Button1Callback); } }
protected string Button2CallbackReference
{ get { return GetCallbackReference(Button2Callback); } }
#endregion
#region page life-cycle events
protected void Page_Init(object sender, EventArgs e)
{
Button1Callback.Raise += Button1Callback_Raise;
Button2Callback.Raise += Button2Callback_Raise;
}
#endregion
#region private methods
private string Button1Callback_Raise(string eventArgument)
{
return "Button 1 callback processed.";
}
private string Button2Callback_Raise(string eventArgument)
{
return "Button 2 callback processed.";
}
private string GetCallbackReference(Control control)
{
return Page.ClientScript.GetCallbackEventReference(control, "arg",
"callback", "context");
}
#endregion
}
}
The ClientCallback
control events get hooked up in the Page_Init
method. Of important notice is that the callback event references need to be assigned to the appropriate ClientCallback
control.
The buttons in this example send an empty string as an argument, and the retrieved result is just a simple text too. However, if you think of exchanging JSON or XML, these client callbacks might become very powerful!
Part III: Custom composite control example
Having client callbacks nicely structured comes in even more handy when it comes to custom web control development. The following composite control implements the same functionality as the page in part II.
using System.Reflection;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: TagPrefix("HelveticSolutions.Web.UI.WebControls", "HelveticSolutions")]
namespace HelveticSolutions.Web.UI.WebControls
{
[ToolboxData("<{0}:SampleCompositeControl
runat="server"></{0}:SampleCompositeControl>")]
public class SampleCompositeControl : CompositeControl
{
#region members
private Button button1;
private Button button2;
private ClientCallback button1Callback;
private ClientCallback button2Callback;
#endregion
protected override void OnPreRender(System.EventArgs e)
{
base.OnPreRender(e);
string clientScript = GetResource("SmartSoft.Web.UI." +
"WebControls.Resources.SampleCompositeControlClientScript.js");
clientScript = clientScript.Replace("{button1_callback_reference}",
GetCallbackReference(button1Callback));
clientScript = clientScript.Replace("{button2_callback_reference}",
GetCallbackReference(button2Callback));
Page.ClientScript.RegisterClientScriptBlock(GetType(),
"client_script", clientScript, true);
}
protected override void CreateChildControls()
{
button1 = new Button
{
ID = "Button1",
Text = "Button 1",
OnClientClick = "button1Clicked('', '', " +
"callbackComplete);return false;"
};
Controls.Add(button1);
button2 = new Button
{
ID = "Button2",
Text = "Button 2",
OnClientClick = "button2Clicked('', '', " +
"callbackComplete);return false;"
};
Controls.Add(button2);
button1Callback = new ClientCallback {ID = "button1Callback"};
button1Callback.Raise += button1Callback_Raise;
Controls.Add(button1Callback);
button2Callback = new ClientCallback {ID = "button2Callback"};
button2Callback.Raise += button2Callback_Raise;
Controls.Add(button2Callback);
}
protected override void RenderContents(HtmlTextWriter writer)
{
button1.RenderControl(writer);
button2.RenderControl(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Id, "result");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.RenderEndTag();
button1Callback.RenderControl(writer);
button2Callback.RenderControl(writer);
}
private string button1Callback_Raise(string eventArgument)
{
return "Button 1 callback processed.";
}
private string button2Callback_Raise(string eventArgument)
{
return "Button 2 callback processed.";
}
private static string GetResource(string resourceName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
string result = string.Empty;
Stream resourceStream =
assembly.GetManifestResourceStream(resourceName);
if (resourceStream != null)
{
using (TextReader textReader =
new StreamReader(resourceStream))
{
result = textReader.ReadToEnd();
}
}
return result;
}
private string GetCallbackReference(Control control)
{
return Page.ClientScript.GetCallbackEventReference(control, "arg",
"callback", "context");
}
}
}
The client script is embedded as a resource in the assembly, and it contains two place holders that get replaced by the actual callback event references in the OnPreRender
methods.
It is important that the ClientCallback
controls get an ID assigned. Otherwise, ASP.NET fails to allocate the client callback to the right control.
History
- 07/01/2009 - Initial post.
- 14th November 2014 - Namespace corrected