Handle multiple client callbacks in ASP.NET






4.77/5 (14 votes)
A smart way to handle multiple client callbacks in ASP.NET pages and controls.
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
{
/// <summary>
/// This control encapsulates a client callback and redirects it
/// through an exposed event.
/// </summary>
[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
///<summary>
/// Processes a callback event that targets this control.
///</summary>
///<param name="eventArgument">A string that represents an event argument to
/// pass to the event handler.</param>
public void RaiseCallbackEvent(string eventArgument)
{
if (Raise != null) callbackResult = Raise(eventArgument);
}
///<summary>
///Returns the results of a callback event that targets this control.
///</summary>
///<returns>The result of the callback.</returns>
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)
{
// Register client callback events
Button1Callback.Raise += Button1Callback_Raise;
Button2Callback.Raise += Button2Callback_Raise;
}
#endregion
#region private methods
/// <summary>
/// Handles client callback events for button 1.
/// </summary>
/// <param name="eventArgument">The event argument
/// of the callback.</param>
/// <returns>The result of the callback.</returns>
private string Button1Callback_Raise(string eventArgument)
{
return "Button 1 callback processed.";
}
/// <summary>
/// Handles client callback events for button 2.
/// </summary>
/// <param name="eventArgument">The event argument
/// of the callback.</param>
/// <returns>The result of the callback.</returns>
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);
// Register client side script for the callbacks
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()
{
// Create buttons
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);
// Create callback controls
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)
{
// Render buttons
button1.RenderControl(writer);
button2.RenderControl(writer);
// Render result div
writer.AddAttribute(HtmlTextWriterAttribute.Id, "result");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.RenderEndTag();
// Render callback controls
button1Callback.RenderControl(writer);
button2Callback.RenderControl(writer);
}
/// <summary>
/// Handles client callback events for button 1.
/// </summary>
/// <param name="eventArgument">The event argument
/// of the callback.</param>
/// <returns>The result of the callback.</returns>
private string button1Callback_Raise(string eventArgument)
{
return "Button 1 callback processed.";
}
/// <summary>
/// Handles client callback events for button 2.
/// </summary>
/// <param name="eventArgument">The event argument
/// of the callback.</param>
/// <returns>The result of the callback.</returns>
private string button2Callback_Raise(string eventArgument)
{
return "Button 2 callback processed.";
}
/// <summary>
/// Helper to load embedded resource as a string.
/// </summary>
/// <param name="resourceName">Resource name.</param>
/// <returns>A string that represents the resource content.</returns>
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