Common PostbackManager
Common_PostbackManager_CSharp\PostbackManager_CSharp\PostbackManager.cs
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

namespace PostbackManager_CSharp
{
    #region PostbackManager Implementation

    [AjaxControlToolkit.ClientScriptResource("PostbackManager_CSharp.PostbackManager", 
        "PostbackManager_CSharp.JavaScript.PostbackManager.js")]
    public class PostbackManager: ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl
    {
        #region Member Variables

        // Stores the argument passed to the server during asynchronous requests
        private string _callbackArgument = null;
        // Stores user-specified custom arguments to be passed to the client tier requestProcessed event
        private string _customResults = null;

        // Delegate for the RequestReceived events
        private PostbackManager_CSharp.RequestReceivedEventHandler _requestReceivedHandler;

        #endregion

        #region Public Properties - ExposeAdfRequestEvents, CustomResults

        /// <summary>
        /// Enables event handling for requests initiated by Web ADF controls.  When true, all Web ADF requests
        /// will be re-routed through the PostbackManager.
        /// </summary>
        [System.ComponentModel.Category("PostbackManager"),
         System.ComponentModel.DefaultValue(true),
         System.ComponentModel.Description("Enables event handling for requests initiated by Web ADF controls"),
         System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute)]
        public bool ExposeAdfRequestEvents
        {
            get
            {
                return (this.StateManager.GetProperty("ExposeAdfRequestEvents") == null) ? true :
                    (bool)this.StateManager.GetProperty("ExposeAdfRequestEvents");
            }
            set { this.StateManager.SetProperty("ExposeAdfRequestEvents", value); }
        }

        /// <summary>
        /// Stores properties that are returned to the client via the arguments object passed to the client tier
        /// requestProcessed event.  Can be used to pass data to the client without manipulating callback results.
        /// </summary>
        [System.ComponentModel.Browsable(false)]
        public string CustomResults
        {
            get { return _customResults; }
            set { _customResults = value; }
        }

        #endregion

        #region ASP.NET WebControl Life Cycle Event Overrides - OnPreRender, RenderContents

        // Registers script to create the client tier singleton PostbackManager instance
        protected override void OnPreRender(System.EventArgs e)
        {
            base.OnPreRender(e);

            // Only execute initialization logic during full page postbacks
            if (base.IsAsync)
                return;

            // Initialize a dictionary with the properties needed to initialize the client tier
            // PostbackManager singleton instance
            System.Collections.Generic.Dictionary<string, object> clientPropertyDictionary =
                new System.Collections.Generic.Dictionary<string, object>();
            clientPropertyDictionary.Add("uniqueID", this.UniqueID);
            clientPropertyDictionary.Add("callbackFunctionString", this.CallbackFunctionString);
            clientPropertyDictionary.Add("exposeAdfRequestEvents", this.ExposeAdfRequestEvents);

            // Serialize the properties to JSON
            System.Web.Script.Serialization.JavaScriptSerializer jsSerializer =
                new System.Web.Script.Serialization.JavaScriptSerializer();
            string clientPropertyJson = jsSerializer.Serialize(clientPropertyDictionary);

            // Construct JavaScript to instantiate the client tier PostbackManager singleton
            string scriptKey = "ESRI_PostbackManager_Script";
            string initializationScript = @"Sys.Application.add_init(function() {{
                if (ESRI.ADF.Samples.PostbackManager)
                    return;

                ESRI.ADF.Samples.PostbackManager = 
                    $create(ESRI.ADF.Samples._PostbackManager, {0}, null, null, $get('{1}'));
            }});";            
            initializationScript = string.Format(initializationScript, clientPropertyJson, this.ClientID);

            // Register the script to execute on application startup
            System.Web.UI.ScriptManager.RegisterStartupScript(this, this.GetType(), scriptKey, 
                initializationScript, true);
        }

        // Creates a design-time representation of the control
        protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
        {
            if (this.DesignMode)
                this.RenderDesignTimeHtml(writer);
            else
                base.RenderContents(writer);
        }

        #endregion

        #region ICallbackEventHandler Overrides - GetCallbackResult, RaiseCallbackEvent

        // Initiates callback events on the calling control, raises the RequestReceived event,
        // and constructs script to invoke the client tier requestProcessed event
        public override string GetCallbackResult()
        {
            // Parse the callback arguments into a name-value collection
            System.Collections.Specialized.NameValueCollection callbackArgs =
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(_callbackArgument);
            string callingControlID = callbackArgs["ControlID"];

            // Make sure a calling control was specified in the request arguments
            if (callingControlID != null)
            {
                // Retrieve the calling control
                System.Web.UI.Control callingControl = 
                    PostbackManager_CSharp.PostbackManager.FindControlByClientID(this.Page, callingControlID);

                // Fire the RequestReceived event
                PostbackManager_CSharp.AdfRequestEventArgs requestReceivedArgs =
                    new PostbackManager_CSharp.AdfRequestEventArgs(callingControl, _callbackArgument);
                this.OnRequestReceived(requestReceivedArgs);

                // Invoke ICallbackEventHandler members on the calling control if that control is not the 
                // PostbackManager.  This also copies callback results from the calling control.
                if (callingControl != this)
                    this.DoCallerCallback(callingControl, _callbackArgument);       

                // Convert callback results into a JSON serializable object
                System.Web.Script.Serialization.JavaScriptSerializer jsSerializer =
                    new System.Web.Script.Serialization.JavaScriptSerializer();
                object callbackResults = null;
                if (this.CallbackResults.ToString() != null)
                    callbackResults = jsSerializer.DeserializeObject(this.CallbackResults.ToString());

                // Initialize a dictionary with the results to be returned to the client tier 
                // requestProcessed event
                System.Collections.Generic.Dictionary<string, object> resultsDictionary =
                    new System.Collections.Generic.Dictionary<string, object>();
                resultsDictionary.Add("callingControlID", callingControlID);
                resultsDictionary.Add("callbackResults", callbackResults);
                resultsDictionary.Add("customResults", this.CustomResults);

                // Serialize the results to JSON
                string resultsJson = jsSerializer.Serialize(resultsDictionary);               

                // Embed JavaScript to invoke the client tier requestProcessed event in a callback result
                string raiseRequestProcessedScript = string.Format(
                    "ESRI.ADF.Samples.PostbackManager._raiseEvent('requestProcessed', {0});", resultsJson);
                this.CallbackResults.Add(
                    ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(raiseRequestProcessedScript));
            }

            return this.CallbackResults.ToString();
        }

        // Retrieves the argument passed to the server
        public override void RaiseCallbackEvent(string eventArgument)
        {
            base.RaiseCallbackEvent(eventArgument);
            _callbackArgument = eventArgument;
        }

        #endregion

        #region IPostBackEventHandler Overrides - RaisePostBackEvent

        // Invokes web tier asynchronous request handling logic
        public override void RaisePostBackEvent(string eventArgument)
        {
            this.RaiseCallbackEvent(eventArgument);
            base.RaisePostBackEvent(eventArgument);
        }

        #endregion

        #region Event Wiring - RequestReceived

        /// <summary>
        /// Occurs when the web request has reached the server, but before the request has been processed
        /// on the server by the Web ADF control initiating the request.
        /// </summary>
        public event RequestReceivedEventHandler RequestReceived
        {
            add { _requestReceivedHandler += value; }
            remove { _requestReceivedHandler -= value; }
        }

        /// <summary>
        /// Raises the RequestReceived event.
        /// </summary>
        /// <param name="args">A RequestProcessingEventArgs object that contains the Web ADF control that initiated the request.</param>
        protected virtual void OnRequestReceived(PostbackManager_CSharp.AdfRequestEventArgs args)
        {
            if (_requestReceivedHandler != null) _requestReceivedHandler(this, args);
        }

        #endregion

        #region Private Methods

        // Calls RaiseCallbackEvent and GetCallbackResult on the passed-in control and copies callback results to 
        // the PostbackManager
        private void DoCallerCallback(System.Web.UI.Control control, string callbackArgument)
        {
            // Check which type of Web ADF base control the passed-in control is.  Then invoke the control's 
            // callback event methods and copy its callback results.
            if (control is ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl)
            {
                ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl webControl =
                    control as ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl;
                webControl.RaiseCallbackEvent(callbackArgument);
                webControl.GetCallbackResult();
                this.CallbackResults.CopyFrom(webControl.CallbackResults);
            }
            else if (control is ESRI.ArcGIS.ADF.Web.UI.WebControls.CompositeControl)
            {
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CompositeControl compositeControl =
                    control as ESRI.ArcGIS.ADF.Web.UI.WebControls.CompositeControl;
                compositeControl.RaiseCallbackEvent(callbackArgument);
                compositeControl.GetCallbackResult();
                this.CallbackResults.CopyFrom(compositeControl.CallbackResults);
            }
        }

        // Searches the passed-in control and all its child controls, returning that having the passed-in
        // client ID, if found
        private static System.Web.UI.Control FindControlByClientID(System.Web.UI.Control rootControl, string controlID)
        {
            // Return the passed-in control if it has the passed-in ID
            if (rootControl.ClientID == controlID)
                return rootControl;

            // Iterate through the control's child controls, recursively calling FindControl on each.
            // If at any point a control with the passed-in ID is found, return it.
            foreach (System.Web.UI.Control childControl in rootControl.Controls)
            {
                System.Web.UI.Control foundControl = FindControlByClientID(childControl, controlID);
                if (foundControl != null)
                    return foundControl;
            }

            return null;
        }

        // Renders the control at design-time
        private void RenderDesignTimeHtml(System.Web.UI.HtmlTextWriter writer)
        {
            // Create a table to format the design-time display
            System.Web.UI.WebControls.Table table = new System.Web.UI.WebControls.Table();
            table.Font.Name = this.Font.Name;
            table.Font.Size = this.Font.Size;
            table.Style[System.Web.UI.HtmlTextWriterStyle.BackgroundColor] = "white";
            table.BorderColor = System.Drawing.Color.Black;
            table.BorderStyle = System.Web.UI.WebControls.BorderStyle.Solid;
            table.BorderWidth = 1;

            System.Web.UI.WebControls.TableRow row = new System.Web.UI.WebControls.TableRow();
            table.CellPadding = 4;
            table.Rows.Add(row);

            // Add the control's ID
            System.Web.UI.WebControls.TableCell cell = new System.Web.UI.WebControls.TableCell();
            row.Cells.Add(cell);
            cell.Style[System.Web.UI.HtmlTextWriterStyle.WhiteSpace] = "nowrap";
            cell.Text = string.Format("{0}<br>", this.ClientID);

            row = new System.Web.UI.WebControls.TableRow();
            table.Rows.Add(row);

            // Add the control type
            cell = new System.Web.UI.WebControls.TableCell();
            row.Cells.Add(cell);
            cell.Text = "PostbackManager WebControl";

            table.RenderControl(writer);
        }

        #endregion
    }

    #endregion

    #region Event Delegate and Argument Classes - RequestReceivedEventHandler, AdfRequestEventArgs

    /// <summary>Delegate to handle the RequestReceived event.</summary>
    /// <param name="sender">The PostbackManager Control that initiated the event.</param>
    /// <param name="args">Arguments that include the ADF Control that initiated the request.</param>
    public delegate void RequestReceivedEventHandler(object sender, PostbackManager_CSharp.AdfRequestEventArgs args);

    /// <summary>Event arguments for a Web ADF asyncrhonous request.</summary>
    public class AdfRequestEventArgs : System.EventArgs
    {
        private System.Web.UI.Control _callingControl;
        private string _requestArguments;

        /// <summary>Constructor.</summary>
        /// <param name="callingControl">The Web ADF control that initiated the request.</param>
        /// <param name="requestArguments">The arguments passed to the server in the request.</param>
        public AdfRequestEventArgs(System.Web.UI.Control callingControl, string requestArguments)
        {
            _callingControl = callingControl;
            _requestArguments = requestArguments;
        }

        /// <summary>The Web ADF control that initiated the request.</summary>
        public System.Web.UI.Control CallingControl { get { return _callingControl; } }
        /// <summary>The arguments passed along with the request.</summary>
        public string RequestArguments { get { return _requestArguments; } }
    }

    #endregion
}