Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Handle multiple client callbacks in ASP.NET

0.00/5 (No votes)
15 Nov 2014 2  
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 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.

<%@ 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here