Common Custom tasks
Common_CustomTasks_CSharp\PostBackTask_CSharp\PostBackTask.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.
// 

[assembly: System.Web.UI.WebResource("PostBackTask_CSharp.Resources.JavaScript.CustomSendRequest.js", "text/javascript")]

// Classes encapsulating several methods of invoking and handling asynchronous functionality within a task.  Includes 
// buttons that (1) issue a partial postback, (2) invoke a custom callback, (3) execute the task via the task framework
// methods, and (4) invoke a callback that is handled in a class other than that of the parent task.
namespace PostBackTask_CSharp
{
    [System.Web.UI.ToolboxData(@"<{0}:PostBackTask runat=""server"" Width=""200px"" Transparency=""35"" 
        BackColor=""White"" TitleBarColor=""WhiteSmoke"" TitleBarSeparatorLine=""False"" 
        TitleBarHeight=""20px"" BorderColor=""LightSteelBlue"" BorderStyle=""Outset"" BorderWidth=""1px"" 
        Font-Names=""Verdana"" Font-Size=""8pt"" ForeColor=""Black""> </{0}:PostBackTask>")]

    public class PostBackTask : ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanelTask
    {
        #region Instance Variable Declarations

        private PostBackTask_CSharp.CustomCallbackButton _handleCallbackButton;
        private System.Web.UI.WebControls.Button _handlePartialPostBackButton;
        private System.Web.UI.HtmlControls.HtmlInputHidden _hiddenHtmlInput;
        private System.Collections.Generic.List<string> _inputControlList = 
            new System.Collections.Generic.List<string>();
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults _taskResults;
        private string _taskJobID;

        #endregion

        #region ASP.NET WebControl Event Handlers - OnLoad, CreateChildControls, OnPreRender, HandlePartialPostBackButton_Click

        #region Life Cycle Event Handlers - OnLoad, CreateChildControls, OnPreRender

        protected override void OnLoad(System.EventArgs e)
        {
            base.OnLoad(e);

            // Pass the task instance to the CustomCallbackButton if the Callback framework
            // is being used (i.e. a ScriptManager is not on the page)
            if (_handleCallbackButton != null)
                _handleCallbackButton.PostBackTaskInstance = this;
        }

        protected override void CreateChildControls()
        {
            Controls.Clear();
            base.CreateChildControls();

            // If there is no ScriptManager on the page, initialize and add custom callback button to the task.
            // Otherwise, add a partial postback button.
            if (this.ScriptManager == null)
            {
                #region Custom Callback Button Instantiation

                // Initialize a custom callback button and add it to the task's controls collection
                _handleCallbackButton = new CustomCallbackButton();
                _handleCallbackButton.ID = "CallbackButton";
                _handleCallbackButton.Text = "Handle Callback";
                _handleCallbackButton.UseSubmitBehavior = false;

                Controls.Add(_handleCallbackButton);

                #endregion
            }
            else
            {
                #region Partial PostBack Button Instantiation

                // Initialize a button that will trigger a partial postback
                _handlePartialPostBackButton = new System.Web.UI.WebControls.Button();
                _handlePartialPostBackButton.ID = "partialPostBackButton";
                _handlePartialPostBackButton.Text = "Handle Partial PostBack";
                _handlePartialPostBackButton.UseSubmitBehavior = false;

                // Wire a handler for the button's click event
                _handlePartialPostBackButton.Click += new System.EventHandler(HandlePartialPostBackButton_Click);

                // Regiester the button as an asyncrhonous postback control so it triggers a partial postback
                // when clicked
                ScriptManager.RegisterAsyncPostBackControl(_handlePartialPostBackButton);

                // Add a custom attribute to the button to be handled during the partial postback
                _handlePartialPostBackButton.Attributes.Add("buttonParameter", "buttonValue");

                // Add the button to the task's controls collection
                Controls.Add(_handlePartialPostBackButton);

                #endregion

                // Initialize a hidden element and add it to the task's controls collection
                _hiddenHtmlInput = new System.Web.UI.HtmlControls.HtmlInputHidden();
                _hiddenHtmlInput.ID = "hiddenTag";
                _hiddenHtmlInput.Value = "";

                Controls.Add(_hiddenHtmlInput);
            }

            #region Controls for Task Input - TextBox and DropDownList

            // Create a textbox and add it to the task's controls collection
            System.Web.UI.WebControls.TextBox textBox = new System.Web.UI.WebControls.TextBox();
            textBox.ID = "PostBackTaskTextBox";
            Controls.Add(textBox);

            // Add the textbox's ID to the list of input control IDs
            _inputControlList.Add(textBox.ClientID);

            // Create a drop-down list, add items to it, and add it to the task's controls collection
            System.Web.UI.WebControls.DropDownList dropDownList = new System.Web.UI.WebControls.DropDownList();
            dropDownList.ID = "PostBackTaskDropDownList";
            dropDownList.Items.Add("item 1");
            dropDownList.Items.Add("item 2");
            dropDownList.Items.Add("item 3");
            Controls.Add(dropDownList);

            // Add the drop-down list's ID to the list of input controls IDs
            _inputControlList.Add(dropDownList.ClientID);

            #endregion

            #region Button Invoking Asynchronous Request via Custom JavaScript

            // Construct a JavaScript string which, when evaluated, will result in a string of the input
            // control IDs, each separated by "~~~"
            System.Text.StringBuilder customArgumentBuilder = new System.Text.StringBuilder("'input=' + ");
            for (int i = 0; i < _inputControlList.Count; i++)
            {
                customArgumentBuilder.Append(string.Format("ESRI.ADF.System.escapeForCallback($get('{0}').value)",
                    _inputControlList[i]));
                if (i + 1 == _inputControlList.Count)
                    break;
                customArgumentBuilder.Append(" + '~~~' + ");
            }
            string customArguments = customArgumentBuilder.ToString();

            // Create a button which, when clicked, will initiate an asynchronous request via custom JavaScript
            System.Web.UI.WebControls.Button customAsyncRequestButton = new System.Web.UI.WebControls.Button();
            customAsyncRequestButton.Text = "Send Custom Request";

            // Construct JavaScript to initiate an asyncrhonous request via the task's callback function string
            string sendAsyncRequestJavaScript = string.Format("sendRequest({0}, \"{1}\");return false;",
                customArguments, this.CallbackFunctionString);

            // Wire the asynchronous request initiation script to execute when the button is clicked
            customAsyncRequestButton.OnClientClick = sendAsyncRequestJavaScript;

            // Add the button to the task's controls collection
            Controls.Add(customAsyncRequestButton);

            #endregion

            #region Button Initiating Task Execution via Web ADF Task Framework

            // Create a button for task execution
            System.Web.UI.WebControls.Button executeTaskButton = new System.Web.UI.WebControls.Button();
            executeTaskButton.Text = "Execute Task";

            // Construct JavaScript that calls the Web ADF's executeTask function.  This will initiate
            // a callback that invokes the task's ExecuteTask method on the server.
            string executeTaskJavaScript = string.Format("ESRI.ADF.Tasks.executeTask({0}, \"{1}\");return false;",
                customArguments, CallbackFunctionString);

            // Wire the JavaScript to execute when the button is clicked
            executeTaskButton.OnClientClick = executeTaskJavaScript;

            // Add the button to the task's controls colleciton
            Controls.Add(executeTaskButton);

            #endregion
        }

        protected override void OnPreRender(System.EventArgs e)
        {
            base.OnPreRender(e);

            // Register the CustomSendRequest JavaScript file if it has not already been
            string embedTaskScriptKey = "embeddedPostBackTaskScript";
            if (!Page.ClientScript.IsClientScriptBlockRegistered(embedTaskScriptKey))
            {
                string scriptLocation = Page.ClientScript.GetWebResourceUrl(typeof(PostBackTask), 
                    "PostBackTask_CSharp.Resources.JavaScript.CustomSendRequest.js");
                Page.ClientScript.RegisterClientScriptInclude(embedTaskScriptKey, scriptLocation);
            }

            // Check whether the Callback or Partial PostBack framework is being used by checking whether
            // a ScriptManager has been included on the page.  Then wire client-side logic accordingly.
            if (this.ScriptManager == null)
            {
                #region Custom Callback Button Click Handling

                // Get the JavaScript syntax for invoking a callback to the callback button
                string callbackInvocationString = Page.ClientScript.GetCallbackEventReference(
                    _handleCallbackButton, "argument", "processCallbackResult", "context");

                // Construct JavaScript to create an argument string containing the browser's dimensions
                // and execute a callback to pass the argument to the server.
                string callbackJavaScript = @"
                        var argument = 'callbackArgument=' + document.body.clientWidth + '~~~' 
                            + document.body.clientHeight;
                        var context = null;
                        {0};
                        return;
                    ";
                // Substitute the callback invocation into the callback packaging JavaScript
                callbackJavaScript = string.Format(callbackJavaScript, callbackInvocationString);

                // Wire the script to the callback button's click event
                _handleCallbackButton.OnClientClick = callbackJavaScript;

                #endregion
            }
            else
            {
                #region Partial PostBack Button Click Handling

                // Get the JavaScript syntax to invoke a postback to the partial postback button
                string postbackInvocationString = Page.ClientScript.GetPostBackEventReference(
                    _handlePartialPostBackButton, string.Empty);

                // Construct JavaScript to place an argument string in a hidden input element and invoke
                // a postback
                string postbackJavaScript = @"
                        $get('{0}').value = 'postbackArgument=' + document.body.clientWidth + '~~~' 
                            + document.body.clientHeight;
                        {1};
                        return;
                    ";
                // Substitute the hidden input's client ID and the postback invocation syntax into the 
                // argument packaging JavaScript
                postbackJavaScript = string.Format(postbackJavaScript, _hiddenHtmlInput.ClientID, 
                    postbackInvocationString);

                // Wire the script to the postback button's click event
                _handlePartialPostBackButton.OnClientClick = postbackJavaScript;

                #endregion

                #region Partial PostBack Response Handling

                // Create the JavaScript necessary to pass the result of a partial postback to the Web ADF
                // JavaScript library's processCallbackResult function
                string scriptKeyCustom = "customDataItemScript";
                if (!this.Page.ClientScript.IsStartupScriptRegistered(GetType(), scriptKeyCustom))
                {
                    string processDataItemJavaScript = @"
                
                        function onLoadFunction(){{
                          Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(AsyncResponseHandler);
                        }}

                        function AsyncResponseHandler(sender, args) {{
                          var dataItems = args.get_dataItems();
                          if (dataItems['{0}'] != null)
                            ESRI.ADF.System.processCallbackResult(dataItems['{0}']);
                        }}

                        Sys.Application.add_init(onLoadFunction);";
                    processDataItemJavaScript = string.Format(processDataItemJavaScript, this.Page.ClientID);

                    // Register the script as a startup script so the result handling code is wired during
                    // applicaton initialization
                    this.Page.ClientScript.RegisterStartupScript(GetType(), scriptKeyCustom, 
                        processDataItemJavaScript, true);

                }

                #endregion
            }
        }

        #endregion

        #region UI Event Handlers - HandlePartialPostBackButton_Click

        // Fires when the Handle Partial PostBack button is clicked
        void HandlePartialPostBackButton_Click(object sender, System.EventArgs e)
        {
            // Get the contents of the hidden input element from the page's request parameters.  Since
            // the asynchronous request is a partial postback and the input element is a server control,
            // we know that this is included in the page request.
            string hiddenValueArguments = Page.Request.Params[_hiddenHtmlInput.UniqueID];

            // Use the Web ADF's callback parsing utility to split the contents of the hidden element into
            // a name-value collection.  Note that the parsing utility can be used on any string with the
            // format <Param Name 1>=<Value 1>&<Param Name 2>=<Value 2>&...
            System.Collections.Specialized.NameValueCollection postbackArgsCollection =
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(
                hiddenValueArguments);

            // Get the postback argument from the collection
            string postbackArguments = postbackArgsCollection["postbackArgument"];

            if (!string.IsNullOrEmpty(postbackArguments))
            {
                // Split the argument string into the input parameters contained within it.  Note the 
                // JavaScript formatting this string was registered in OnPreRender.
                string[] inputArray = postbackArguments.Split(new string[] { "~~~" }, 
                    System.StringSplitOptions.None);

                // Format a simple task result with the input arguments and display the result in the task
                // results container
                ESRI.ArcGIS.ADF.Web.UI.WebControls.SimpleTaskResult simpleTaskResult =
                    new ESRI.ArcGIS.ADF.Web.UI.WebControls.SimpleTaskResult("Browser Dimensions", 
                    "Width: " + inputArray[0] + "," + "Height: " + inputArray[1]);
                TaskResultsInstance.DisplayResults(null, null, null, simpleTaskResult);    
            }
   
            // Zoom the map
            if (TaskResultsInstance.MapInstance != null)
                TaskResultsInstance.MapInstance.Zoom(2);

            // Copy the map's callback results to the task results container's callback results collection
            TaskResultsInstance.CallbackResults.CopyFrom(TaskResultsInstance.MapInstance.CallbackResults);

            // Register the task results container's callback results as a data item.  This will pass the
            // callback results to the client-side AsyncResponseHandler function that was registered in
            // OnPreRender.  This function, in turn, will pass the callback results to the Web ADF's 
            // processCallbackResults function.
            this.ScriptManager.RegisterDataItem(Page, TaskResultsInstance.CallbackResults.ToString(), false);
        }

        #endregion

        #endregion

        #region ICallbackEventHandler Member Overrides - GetCallbackResult

        // Fires when the callback framework is being used and an asynchronous request is issued to the task
        public override string GetCallbackResult()
        {
            // Use the Web ADF's callback parsing utility to split the argument passed in the callback into
            // a collection of name-value pairs
            System.Collections.Specialized.NameValueCollection callbackArgsCollection =
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(
                this.CallbackEventArgument);
            
            // Get the value of the EventArg parameter
            string eventArgument = callbackArgsCollection["EventArg"];

            // Get the task input
            string inputString = callbackArgsCollection["input"];

            // Clear the server-side property storing task input
            this.Input = null;
            if (!string.IsNullOrEmpty(inputString))
            {
                // Parse the input string into an array of input arguments
                string[] inputArray = inputString.Split(new string[] { "~~~" }, System.StringSplitOptions.None);
                // Set the task's server-side input property to reference the input array
                this.Input = inputArray;
            }

            // If the event argument is "executeTask," the ExecuteTask method will fire automatically.  
            // Otherwise, we must explicitly invoke any server side methods we want to execute during the
            // callback.
            if (eventArgument == "executeTask")
            {
                // Get the job ID of the task.  This will be used to set up the task result node containing
                // the results
                _taskJobID = callbackArgsCollection["taskJobID"];

                // Since we are using the task framework, we can simply return the base method's result
                return base.GetCallbackResult();
            }
            else if (eventArgument == "customRequest")
            {
                // Invoke the method we want to execute during the callback.
                CustomMethod();
            }

            // Since we have invoked server-side logic outside the scope of the task framework, we explicitly
            // return the callback results that have been created during execution of that logic.
            return this.CallbackResults.ToString();
        }

        #endregion

        #region FloatingPanelTask Overrides - ExecuteTask, GetGISResourceItemDependencies

        // Fires when a callback is initiated by invoking the Web ADF's executeTask function from the client
        public override void ExecuteTask()
        {
            // Make sure that task input exists
            if (this.Input == null)
                return;

            // Cast the task input to a string array
            string[] inputArray = this.Input as string[];

            // Clear out the property storing the task results
            this.Results = null;

            // Create a Task Result Node, set its text to the first input paramter, and initialize 
            // it via the SetupTaskResultNode method.
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode taskResultNode = 
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode();
            taskResultNode.Text = inputArray[0];
            TaskResultsInstance.SetupTaskResultNode(this, _taskJobID, Input, taskResultNode);

            // Create a TreeViewPlus Node, set its text to the second input parameter, and add it 
            // to the node collection of the Task Result Node
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode treeViewPlusNode = 
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode();
            treeViewPlusNode.Text = inputArray[1];
            taskResultNode.Nodes.Add(treeViewPlusNode);

            // Zoom the map
            if (TaskResultsInstance.MapInstance != null)
                TaskResultsInstance.MapInstance.Zoom(2);

            // Set the Task Result Node as the result of the task
            this.Results = taskResultNode;
        }

        // Used by Manager to see if task requires a particular resource item in a resource manager.  
        // Since this task has no dependencies, the implementation here returns an empty list.
        public override System.Collections.Generic.List<
            ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency> GetGISResourceItemDependencies()
        {
            return new System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency>();
        }

        #endregion

        #region Instance Members - CustomMethod, TaskResultsInstance

        #region Instance Methods - Custom Method 

        // Fires when the Send Custom Request button is clicked
        public void CustomMethod()
        {
            // Make sure task input exists
            if (this.Input == null)
                return;

            // Cast the task's input to a string array
            string[] inputArray = this.Input as string[];

            // Create a task result node, set its text as the first item in the input array, and initialze 
            // a context menu for it
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode taskResultNode = 
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode();
            taskResultNode.Text = inputArray[0];
            TaskResultsInstance.SetupContextMenu(TaskResultsInstance.RemoveOnlyContextMenu, taskResultNode);

            // Initialize a tree view plus node, set its text to the second item in the input array, and
            // add it to the nodes collection of the task result node
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode treeViewPlusNode = 
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode();
            treeViewPlusNode.Text = inputArray[1];
            taskResultNode.Nodes.Add(treeViewPlusNode);

            // Add the task result node to the nodes collection of the task's task results container
            TaskResultsInstance.Nodes.Add(taskResultNode);
            
            // Refresh the task results container to apply the addition of the task results node
            TaskResultsInstance.Refresh();

            // Copy the callback results of the task results container to the task's callback results 
            // collection.  GetCallbackResults returns the task's callback results collection to the
            // client, so we need to do this so the task results container's callback results are 
            // processed on the client.
            this.CallbackResults.CopyFrom(TaskResultsInstance.CallbackResults);
        }

        #endregion

        #region Instance Properties - TaskResultsInstance

        // Convenient access to the first TaskResults control in the Task's TaskResultsContainers collection
        internal ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults TaskResultsInstance
        {
            get
            {
                // Retrieve the TaskResults control if it has not already been
                if ((_taskResults == null) && (this.TaskResultsContainers[0] != null))
                    _taskResults = ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(
                        this.TaskResultsContainers[0].Name, this.Page)
                        as ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults;

                return _taskResults;
            }
        }

        #endregion

        #endregion
    }

    // Class encapsulating an ASP.NET button that handles a callback.  Implemented here to show how a task can 
    // include child controls that process their own callbacks, rather than sending those callback to the parent
    // task.
    public class CustomCallbackButton : System.Web.UI.WebControls.Button, System.Web.UI.ICallbackEventHandler
    {
        #region Instance Variable Declarations

        private string _callbackArg;
        private PostBackTask_CSharp.PostBackTask _postbackTask;

        #endregion

        #region ICallbackEventHandler Members - RaiseCallbackEvent, GetCallbackResult

        // The first method to execute on the server when a callback is invoked from the client.  The string
        // containing the callback's parameters is passed to this method.
        public void RaiseCallbackEvent(string eventArgument)
        {
            // Store the callback argument string in an instance variable for reference in GetCallbackResult
            _callbackArg = eventArgument;
        }

        // Fires when a callback string referencing a CustomCallbackButton instance is invoked from the client.  
        // In the PostBackTask implementation above, the JavaScript needed to do that is wired to a 
        // CustomCallbackButton instance in the OnPreRender method.
        public string GetCallbackResult()
        {
            // Use the Web ADF's callback parsing utility to split the callback argument string into a 
            // collection of name-value pairs.
            System.Collections.Specialized.NameValueCollection callbackArgsCollection =
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(
                _callbackArg);

            // Get the callback argument from the collection
            string callbackArguments = callbackArgsCollection["callbackArgument"];

            if (!string.IsNullOrEmpty(callbackArguments))
            {
                // Split the callback argument into a string array
                string[] inputArray = callbackArguments.Split(new string[] { "~~~" }, System.StringSplitOptions.None);

                // Create a simple task result containing the input arguments in the array
                ESRI.ArcGIS.ADF.Web.UI.WebControls.SimpleTaskResult simpleTaskResult =
                    new ESRI.ArcGIS.ADF.Web.UI.WebControls.SimpleTaskResult("Browser Dimensions",
                    "Width: " + inputArray[0] + "," + "Height: " + inputArray[1]);

                // Display the task result in the task results container buddied to the callback button's parent
                // PostBackTask.
                this.PostBackTaskInstance.TaskResultsInstance.DisplayResults(null, null, null, simpleTaskResult);
            }

            // Zoom the map buddied to the callback button's parent PostBackTask
            if (this.PostBackTaskInstance.TaskResultsInstance.MapInstance != null)
                this.PostBackTaskInstance.TaskResultsInstance.MapInstance.Zoom(2);

            // Copy the map's callback results to the task results container's results collection
            this.PostBackTaskInstance.TaskResultsInstance.CallbackResults.CopyFrom(
                 this.PostBackTaskInstance.TaskResultsInstance.MapInstance.CallbackResults);


            return PostBackTaskInstance.TaskResultsInstance.CallbackResults.ToString();
        }

        #endregion

        #region Instance Properties - PostBackTaskInstance

        // Provides access to the parent task
        internal PostBackTask PostBackTaskInstance
        {
            get { return _postbackTask; }
            set { _postbackTask = value; }
        }

        #endregion
    }
}