Click here to Skip to main content
15,867,835 members
Articles / Web Development / ASP.NET

Handle multiple client callbacks in ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.77/5 (18 votes)
15 Nov 2014CPOL3 min read 55.7K   587   59   8
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:

C#
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 Buttons that initiate the callbacks. For the simplicity of this example, both Buttons 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.

ASP.NET
<%@ 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:

C#
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.

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Swissworx
Australia Australia
MCAD, MCPD Web Developer 2.0, MCPD Enterprise Developer 3.5

My company: Swissworx
My blog: Sitecore Experts

Hopp Schwiiz Smile | :)

Comments and Discussions

 
Generalyour customcontrol example is hitting page_load Pin
MartyK20079-Jun-09 15:39
MartyK20079-Jun-09 15:39 
GeneralRe: your customcontrol example is hitting page_load Pin
Michael Ulmann9-Jun-09 18:26
Michael Ulmann9-Jun-09 18:26 
GeneralIT IS Beautiful Pin
suresh_kumar_s10-Feb-09 18:52
suresh_kumar_s10-Feb-09 18:52 
GeneralRe: IT IS Beautiful Pin
Michael Ulmann15-Nov-14 9:48
Michael Ulmann15-Nov-14 9:48 
GeneralMy vote of 2 Pin
kcj0357200413-Jan-09 0:34
kcj0357200413-Jan-09 0:34 
GeneralRe: My vote of 2 Pin
Michael Ulmann12-Feb-09 12:33
Michael Ulmann12-Feb-09 12:33 
I wonder what kinda doco you would expect dude.
GeneralArticle content already exists. Pin
Tim Hammer7-Jan-09 18:55
Tim Hammer7-Jan-09 18:55 
GeneralRe: Article content already exists. Pin
Michael Ulmann7-Jan-09 19:02
Michael Ulmann7-Jan-09 19:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.