Common Custom tasks
Common_CustomTasks_VBNet\ScriptTask_VBNet\ScriptTask.vb
' 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.
' 

Imports Microsoft.VisualBasic
Imports System
Imports System.Web.UI

Namespace ESRI.ADF.Samples.CustomTasks
    ''' <summary>
    ''' When implemented, this class automatically creates a client-side representation of your custom task.
    ''' Any method or control included in the task can be made available on the client-side class by 
    ''' adding the ClientMember attribute to the member declaration.  The client-side class is created as
    ''' an AJAX control with a component ID matching the task's client ID, and can therefore be retrieved
    ''' by a call to $find(task client ID).
    ''' </summary>
    Public MustInherit Class ScriptTask
        Inherits ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanelTask

#Region "ASP.NET WebControl Life Cycle Event Overrides - OnPreRender, Render"

        ' Declares the client-side task class and its members
        Protected Overloads Overrides Sub OnPreRender(ByVal eventArgs As System.EventArgs)
            MyBase.OnPreRender(eventArgs)

            ' Instantiate a StringBuilder to hold the initialization JavaScript
            Dim initializeClientTaskScript As System.Text.StringBuilder = New System.Text.StringBuilder()

            ' Get the type of the task (sub-class) implementing ScriptTask
            Dim subClassType As System.Type = Me.GetType()

            ' Check whether the ClientTaskDefinitionExplicit custom attribute was declared on the 
            ' sub-class.  If so, the attribute instance will be passed to the array.
            Dim definitionExplicitArray As Object() = subClassType.GetCustomAttributes(GetType(ClientTaskDefinitionExplicit), False)

            ' Check whether the ClientTaskJavaScriptResource custom attribute was declared on the 
            ' sub-class.  If so, the attribute instance will be passed to the array.
            Dim javaScriptResourceArray As Object() = subClassType.GetCustomAttributes(GetType(ClientTaskJavaScriptResource), False)

            ' If neither array contains any attributes, then the client task class will need to be
            ' created (i.e. declared) here.  Set a boolean indicating whehter or not this is the case.
            Dim createClientClass As Boolean = (definitionExplicitArray.Length = 0) AndAlso (javaScriptResourceArray.Length = 0)

            ' Check whether the ClientTaskClassName custom attribute was declared on the 
            ' sub-class.  If so, the attribute instance will be passed to the array.
            Dim clientClassNameArray As Object() = subClassType.GetCustomAttributes(GetType(ClientTaskClassName), False)
            If clientClassNameArray.Length > 0 Then
                ' Store the class name specified in the attribute declaration
                Dim clientClassNameAttribute As ClientTaskClassName = TryCast(clientClassNameArray(0), ClientTaskClassName)
                Me.ClientClassName = clientClassNameAttribute.ClassName
            Else
                ' Set the client class name to match that of the server-side task
                Me.ClientClassName = Me.GetType().ToString()
            End If

            If createClientClass Then
                ' The sub-class has not explicitly declared a client-side task class (default).  Generate
                ' JavaScript to declare the class and register it as an AJAX control inheriting from
                ' ESRI.ADF.UI.FloatingPanel.
                Dim classDeclarationScript As String = CreateClassDeclarationScript()
                ' Add the class declaration to the initialization script
                initializeClientTaskScript.Append(classDeclarationScript)
            Else
                ' Initialize a boolean indicating whether a script resource needs to be registered for the 
                ' client-side task
                Dim registerScriptResource As Boolean = True

                ' Check whether the class was declared with a ClientTaskJavaScriptResource attribute
                If javaScriptResourceArray.Length > 0 Then
                    ' Get the path specified by the ClientTaskJavaScriptResource
                    Dim javaScriptResourceAttribute As ClientTaskJavaScriptResource = TryCast(javaScriptResourceArray(0), ClientTaskJavaScriptResource)
                    Me.JavaScriptResourceName = javaScriptResourceAttribute.ResourceName
                Else
                    ' Create a default resource name from the sub-class's task name and the JavaScript file
                    ' extension
                    Me.JavaScriptResourceName = String.Format("{0}.js", Me.GetType().ToString())

                    ' Attempt to retrieve an object containing information about the resource.  If the 
                    ' resource does not exist, this will return null.
                    Dim resourceInfo As System.Reflection.ManifestResourceInfo = subClassType.Assembly.GetManifestResourceInfo(Me.JavaScriptResourceName)

                    ' VB may not prepend the namespace to the resource, so just retrieve using the basic resource name. 
                    If (resourceInfo Is Nothing) Then
                        Me.JavaScriptResourceName = String.Format("{0}.js", Me.GetType().Name.ToString())
                        resourceInfo = subClassType.Assembly.GetManifestResourceInfo(Me.JavaScriptResourceName)
                    End If

                    ' Based on whether the resource was found, set a boolean indicating whether the resource
                    ' should be registered
                    registerScriptResource = (Not resourceInfo Is Nothing)
                End If


                ' Register the client script resource, if necessary
                If registerScriptResource Then
                    If Me.ScriptManager IsNot Nothing Then
                        Dim scriptRef As New ScriptReference(Me.JavaScriptResourceName, subClassType.Assembly.FullName)
                        Me.ScriptManager.Scripts.Add(scriptRef)
                    Else
                        System.Web.UI.ScriptManager.RegisterClientScriptResource(Me, subClassType, Me.JavaScriptResourceName)
                    End If
                End If
            End If

            ' Generate JavaScript that will add server-side members declared with the custom ClientMember
            ' attribute to the client-side task.
            Dim addClientMembersScript As String = CreateAddClientMembersScript()
            ' Add the member-adding logic to the intialization script
            initializeClientTaskScript.Append(addClientMembersScript)

            ' Enclose the initialization script in a function declaration and add the function as a handler 
            ' of the AJAX init event, so the logic executes during application initialization
            Dim classInitFunctionName As String = String.Format("{0}_initializeClientTaskMembers", subClassType.Name)
            initializeClientTaskScript.Insert(0, "function " & classInitFunctionName & "() {")
            initializeClientTaskScript.Append("} Sys.Application.add_init(" & classInitFunctionName & ");")

            Dim scriptKey As String = String.Format("initialize{0}", subClassType.ToString())
            If (Not Me.Page.ClientScript.IsStartupScriptRegistered(scriptKey)) Then
                ' Register the initialization script with the client
                System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType(), scriptKey, initializeClientTaskScript.ToString(), True)
            End If
        End Sub

        ' Instantiates the client-side task
        Protected Overloads Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
            MyBase.Render(writer)

            Dim instatiationFuncName As String = String.Format("create{0}", Me.UniqueID)

            ' FloatingPanelTask, from which ScriptTask inherits, has already instantiated a client-side 
            ' ESRI.ADF.UI.FloatingPanel with an AJAX component ID that matches the task's client ID.
            ' Since the client-side script task inherits from ESRI.ADF.UI.FloatingPanel, we can replace
            ' the already instantiated FloatinPanel with a an instance of our script task.  First, we
            ' must retrieve the existing FloatingPanel and copy its initialization properties.  Then,
            ' the FloatingPanel must be disposed.  Finally, we instantiate the client-side script task
            ' via the AJAX $create function, specifying the client ID of the ScriptTask's sub-class and
            ' the properties of the disposed FloatingPanel.
            '
            ' NOTE: this step must be performed after base.Render because that is where the client-side 
            ' FloatingPanel is instantiated.
            Dim createClientTaskScript As String = "function {0}() {{ " & ControlChars.CrLf & "                    var floatingPanel = $find('{1}');" & ControlChars.CrLf & "                    var properties = {{ id:'{1}', ""transparency"":floatingPanel.get_transparency()," & ControlChars.CrLf & "                        ""callbackFunctionString"":floatingPanel.get_callbackFunctionString()," & ControlChars.CrLf & "                        ""expandedImage"":floatingPanel.get_expandedImage()," & ControlChars.CrLf & "                        ""collapsedImage"":floatingPanel.get_collapsedImage()," & ControlChars.CrLf & "                        ""dockedImage"":floatingPanel.get_dockedImage()," & ControlChars.CrLf & "                        ""undockedImage"":floatingPanel.get_undockedImage()," & ControlChars.CrLf & "                        ""dockedContextMenuID"":floatingPanel.get_dockedContextMenuID()," & ControlChars.CrLf & "                        ""dockParent"":floatingPanel.get_dockParent(), " & ControlChars.CrLf & "                        ""forcePNG"":floatingPanel.get_forcePNG(), " & ControlChars.CrLf & "                        ""isDocked"":floatingPanel.get_isDocked() }};" & ControlChars.CrLf & "                    floatingPanel.dispose();" & ControlChars.CrLf & "                    $create({2}, properties, null, null, $get('{1}'));" & ControlChars.CrLf & "                }}" & ControlChars.CrLf & "                Sys.Application.add_init({0});"
            createClientTaskScript = String.Format(createClientTaskScript, instatiationFuncName, Me.ClientID, Me.ClientClassName)

            System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType(), Me.ClientID & "_createClientTask", createClientTaskScript, True)

            Dim addClientFieldsScript As String = GetAddClientFieldsScript()
            System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType(), Me.ClientID & "_addClientFields", addClientFieldsScript, True)
        End Sub

        Private Function GetAddClientFieldsScript() As String
            ' Instantiate a filter that will be used to find members of the sub-class with the 
            ' ClientMember attribute.  The MemberHasCustomAttribute method passed into the constructor
            ' contains the filtering logic.
            Dim customAttributeFilter As New System.Reflection.MemberFilter(AddressOf MemberHasCustomAttribute)

            ' Find the methods and fields (instance variables) that are marked with the ClientMember
            ' attribute.  The first parameter to FindMembers specifies that instance, static, public,
            ' and non-public fields and methods are to be searched; the second specifies the filter
            ' to use; and the third specifies the attribute to look for.
            Dim clientMemberInfoArray() As System.Reflection.MemberInfo = Me.GetType().FindMembers(System.Reflection.MemberTypes.Method Or System.Reflection.MemberTypes.Field, System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.Static Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.NonPublic, customAttributeFilter, GetType(ClientMember))

            Dim clientMethodInfo As System.Reflection.MethodInfo = Nothing
            Dim clientFieldInfo As System.Reflection.FieldInfo = Nothing

            Dim addClientFieldsScript As New System.Text.StringBuilder()
            addClientFieldsScript.Append("Sys.Application.add_init(function() {" & Constants.vbLf)
            addClientFieldsScript.AppendFormat("var task = $find('{0}');", Me.ClientID)

            ' Iterate through the members found
            For Each memberInfo As System.Reflection.MemberInfo In clientMemberInfoArray
                ' Check whether the current member is a method
                clientMethodInfo = TryCast(memberInfo, System.Reflection.MethodInfo)
                If clientMethodInfo Is Nothing Then
                    ' Since the member is not a method, and only methods and fields were sought in the
                    ' FindMembers call, the member must be a field.  Cast the member to the FieldInfo
                    ' type, from which field-specific information can be retrieved.
                    clientFieldInfo = TryCast(memberInfo, System.Reflection.FieldInfo)

                    ' Make sure the field is a control.  If it is not, skip it.
                    If GetType(System.Web.UI.Control).IsAssignableFrom(clientFieldInfo.FieldType) Then
                        ' Create JavaScript to add a function to the new client-side property that will return the
                        ' client-side task instance (AJAX component).  This allows easy retrieval of the parent
                        ' task when only the element referenced by the property is available.
                        addClientFieldsScript.AppendFormat("task.get_{1}().get_task = function() {{ return $find('{0}'); }};" & Constants.vbLf, Me.ClientID, clientFieldInfo.Name)
                    End If
                End If
            Next memberInfo

            addClientFieldsScript.Append("});" & Constants.vbLf)
            Return addClientFieldsScript.ToString()
        End Function
#End Region

#Region "ICallbackEventHandler Members - GetCallbackResult"

        ' Invokes server-side methods of the ScriptTask's sub-class that have been exposed on the client via
        ' the custom ClientMember attribute.
        Public Overloads Overrides Function GetCallbackResult() As String
            ' Parse the message sent to the server via the callback using the Web ADF's parsing utility
            Dim callbackArgsCollection As System.Collections.Specialized.NameValueCollection = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(Me.CallbackEventArgument)

            ' Make sure the callback event argument is "InvokeMethod" before executing method invocation 
            ' logic
            If (Not callbackArgsCollection("EventArg") Is Nothing) AndAlso (callbackArgsCollection("EventArg") = "InvokeMethod") Then
                ' Get the name of the method to be invoked and the parameters to pass to the method from
                ' the callback argument
                Dim methodName As String = callbackArgsCollection("MethodName")

                Dim methodParameterStrings As String() = callbackArgsCollection("Parameters").Split("|"c)


                ' Instantiate a list to store the methods after they have been cast to the types expected
                ' by the method.  When they are first received from the client, they are all strings.
                Dim methodParameterList As System.Collections.Generic.List(Of Object) = New System.Collections.Generic.List(Of Object)()
                ' Iterate through the methods exposed to the client 
                For Each clientMethodInfo As System.Reflection.MethodInfo In Me.ClientMethodInfoList
                    ' Check whether the current method is the one to be invoked
                    If clientMethodInfo.Name = methodName Then
                        Dim i As Integer = 0
                        ' Iterate through the method's parameters
                        For Each parameterInfo As System.Reflection.ParameterInfo In clientMethodInfo.GetParameters()
                            ' Cast the current parameter's string representation to the type expected by the 
                            ' method and store it in the parameter list
                            methodParameterList.Add(System.Convert.ChangeType(methodParameterStrings(i), parameterInfo.ParameterType))
                            i += 1
                        Next parameterInfo

                        Exit For
                    End If
                Next clientMethodInfo

                ' Convert the parameter list to an array so it can be passed to InvokeMember
                Dim methodParameterArray As Object() = methodParameterList.ToArray()
                ' Get the sub-class type of the task instance that implemented ScriptTask
                Dim subClassType As System.Type = Me.GetType()

                ' Invoke the method on the sub-class.  The first parameter specifies the method to be invoked;
                ' the second specifies the type of members to look for when finding the method to invoke; the
                ' fourth specifies the instance on which to invoke the method; and the fifth specifies the 
                ' arguments to pass to the method invocation.
                Dim result As Object = subClassType.InvokeMember(methodName, System.Reflection.BindingFlags.InvokeMethod Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.Static Or System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic, Nothing, Me, methodParameterArray)

                ' Convert the method result to a string
                Dim resultString As String
                If (result Is Nothing) Then
                    resultString = ""
                Else
                    resultString = result.ToString()
                End If

                ' Construct the JavaScript necessary to fire the method completion logic on the client-side
                ' script task, passing the method result to the client function.
                Dim fireMethodCompleteEvent As String = String.Format("var task = $find('{0}'); " & "task._on{1}Complete('{2}');", Me.ClientID, methodName, resultString)

                ' Create a callback result enapsulating the JavaScript and add it to the task's callback results
                ' so it is executed when program flow returns to the client.
                Dim methodCompleteEventCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(fireMethodCompleteEvent)
                Me.CallbackResults.Add(methodCompleteEventCallbackResult)
            End If

            Return MyBase.GetCallbackResult()
        End Function

#End Region

#Region "FloatingPanelTask Members - ExecuteTask, GetGISResourceItemDependencies"

        ' Overriden here so it does not have to be implemented by sub-classes.
        Public Overloads Overrides Sub ExecuteTask()
            Return
        End Sub

        ' Overriden to return an empty list, indicating that the task has no resource dependencies.  
        ' Overriden here so it does not have to be implemented by sub-classes.
        Public Overloads Overrides Function GetGISResourceItemDependencies() As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency)
            Return New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency)()
        End Function

#End Region

#Region "Instance Properties - ClientMethodInfoList, JavaScriptResourceName, ClientClassName"

        ''' <summary>
        ''' Stores information about all the methods made accessible on the client via the 
        ''' ClientMember attribute
        ''' </summary>
        Private Property ClientMethodInfoList() As System.Collections.Generic.List(Of System.Reflection.MethodInfo)
            Get
                Return TryCast(StateManager.GetProperty("ClientMethodInfo"), System.Collections.Generic.List(Of System.Reflection.MethodInfo))
            End Get
            Set(ByVal value As System.Collections.Generic.List(Of System.Reflection.MethodInfo))
                StateManager.SetProperty("ClientMethodInfo", value)
            End Set
        End Property

        ''' <summary>
        ''' Stores the fully qualified name of the JavaScript file containing the client task's
        ''' definition
        ''' </summary>
        Private Property JavaScriptResourceName() As String
            Get
                Return TryCast(StateManager.GetProperty("JavaScriptResourceName"), String)
            End Get
            Set(ByVal value As String)
                StateManager.SetProperty("JavaScriptResourceName", value)
            End Set
        End Property

        ''' <summary>
        ''' Stores the class name of the client task
        ''' </summary>
        Private Property ClientClassName() As String
            Get
                Return TryCast(StateManager.GetProperty("ClientClassName"), String)
            End Get
            Set(ByVal value As String)
                StateManager.SetProperty("ClientClassName", value)
            End Set
        End Property

#End Region

#Region "Instance Methods - MemberHasCustomAttribute, Script Generation Methods"

        ''' <summary>
        ''' Method used as a delegate to check whether a class member has a particular custom 
        ''' attribute
        ''' </summary>
        ''' <param name="memberInfo">Contains information about the class member to be checked
        ''' </param>
        ''' <param name="filterAttributeObject">The custom attribute sought</param>
        ''' <returns></returns>
        Private Shared Function MemberHasCustomAttribute(ByVal memberInfo As System.Reflection.MemberInfo, ByVal filterAttributeObject As Object) As Boolean
            ' Get the custom attribute as a type
            Dim filterAttributeType As System.Type = CType(filterAttributeObject, System.Type)

            ' Check whether the member referred to by the passed in MemberInfo object has the custom
            ' attribute.  
            Dim customAttributeArray As Object() = memberInfo.GetCustomAttributes(filterAttributeType, False)

            ' If the member has the custom attribute, the method call above will place it in the array.
            ' So return true or false based on whether the array has a length greater than 0.
            Return (customAttributeArray.Length > 0)
        End Function

#Region "Client Task Script Generation Methods"

        ''' <summary>
        ''' Generates JavaScript to declare the client-side task such that it is registered as 
        ''' an AJAX control inheriting from ESRI.ADF.UI.FloatingPanel
        ''' </summary>
        Private Function CreateClassDeclarationScript() As String
            Dim clientTaskNamespace As String = ""
            Dim namespaceRegistrationScript As String = ""
            ' Check whether the client-side task's class name contains a "."  If so, then we need to
            ' ensure that everything preceding the last "." is registered as an AJAX namespace on the
            ' clent.
            If Me.ClientClassName.Contains(".") Then
                ' Get the text preceding the last "." and construct the JavaScript needed to register
                ' that text as an AJAX namespace.
                clientTaskNamespace = Me.ClientClassName.Substring(0, Me.ClientClassName.LastIndexOf("."))
                namespaceRegistrationScript = String.Format("Type.registerNamespace('{0}');", clientTaskNamespace)
            End If

            ' Create JavaScript to declare the client-side task class and register it as an AJAX
            ' control inheriting from ESRI.ADF.UI.FloatingPanel
            Dim classDeclarationScript As String = String.Format("" & ControlChars.CrLf & "                    {0} = function(element) {{" & ControlChars.CrLf & "                        {0}.initializeBase(this, [element]);" & ControlChars.CrLf & "                    }}" & ControlChars.CrLf & "                    {0}.prototype = {{    " & ControlChars.CrLf & "                        initialize : function() {{          " & ControlChars.CrLf & "                            // Initialize the base class" & ControlChars.CrLf & "                            {0}.callBaseMethod(this, 'initialize'); " & ControlChars.CrLf & "                        }},  " & ControlChars.CrLf & "                        // Returns the object's id.  Required for AJAX component registration." & ControlChars.CrLf & "                        get_id : function () {{" & ControlChars.CrLf & "                            return this._id;" & ControlChars.CrLf & "                        }},                    " & ControlChars.CrLf & "                        // Sets the object's id.  Required for AJAX component registration." & ControlChars.CrLf & "                        set_id : function (value) {{" & ControlChars.CrLf & "                            this._id = value;" & ControlChars.CrLf & "                        }}  " & ControlChars.CrLf & "                    }};" & ControlChars.CrLf & ControlChars.CrLf & "                    // Register the task class, specifying inheritance from ESRI.ADF.UI.FloatingPanel." & ControlChars.CrLf & "                    // This will give the task all the properties of FloatingPanel and will make it" & ControlChars.CrLf & "                    // an AJAX control, since FloatingPanel is an AJAX control." & ControlChars.CrLf & "                    {0}.registerClass('{0}', ESRI.ADF.UI.FloatingPanel);", Me.ClientClassName)

            ' Return the namespace and class declaration syntax
            Return namespaceRegistrationScript & classDeclarationScript
        End Function

        ''' <summary>
        ''' Generates JavaScript to expose server-side members that have been declared with the ClientMember
        ''' attribute to the client-side task
        ''' </summary>
        Private Function CreateAddClientMembersScript() As String
            ' Instantiate a StringBuilder to store the JavaScript
            Dim clientMemberDeclarationScript As System.Text.StringBuilder = New System.Text.StringBuilder()

            ' Instantiate a filter that will be used to find members of the sub-class with the 
            ' ClientMember attribute.  The MemberHasCustomAttribute method passed into the constructor
            ' contains the filtering logic.
            Dim customAttributeFilter As System.Reflection.MemberFilter = New System.Reflection.MemberFilter(AddressOf MemberHasCustomAttribute)

            ' Get the type of the task implementing ScriptTask
            Dim subClassType As System.Type = Me.GetType()

            ' Find the methods and fields (instance variables) that are marked with the ClientMember
            ' attribute.  The first parameter to FindMembers specifies that instance, static, public,
            ' and non-public fields and methods are to be searched; the second specifies the filter
            ' to use; and the third specifies the attribute to look for.
            Dim clientMemberInfoArray As System.Reflection.MemberInfo() = subClassType.FindMembers(System.Reflection.MemberTypes.Method Or System.Reflection.MemberTypes.Field, System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.Static Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.NonPublic, customAttributeFilter, GetType(ClientMember))

            Dim clientMethodInfo As System.Reflection.MethodInfo = Nothing
            Dim clientFieldInfo As System.Reflection.FieldInfo = Nothing
            ' Iterate through the members found
            For Each memberInfo As System.Reflection.MemberInfo In clientMemberInfoArray
                ' Check whether the current member is a method
                clientMethodInfo = TryCast(memberInfo, System.Reflection.MethodInfo)
                If clientMethodInfo Is Nothing Then
                    ' Since the member is not a method, and only methods and fields were sought in the
                    ' FindMembers call, the member must be a field.  Cast the member to the FieldInfo
                    ' type, from which field-specific information can be retrieved.
                    clientFieldInfo = TryCast(memberInfo, System.Reflection.FieldInfo)

                    ' Make sure the field is a control.  If it is not, program flow will go to the next
                    ' loop iteration.
                    If GetType(System.Web.UI.Control).IsAssignableFrom(clientFieldInfo.FieldType) Then
                        ' Generate the JavaScript needed to expose the control on the client-side task.
                        ' The control will have the same name as specified in the server-side declaration
                        ' and will refere to the control's client-side DOM element.
                        Dim addControlScript As String = CreateAddClientControlScript(clientFieldInfo)
                        clientMemberDeclarationScript.Append(addControlScript)
                    End If
                Else
                    ' The current member is a method.

                    ' If the list storing information about all the methods exposed to the client hasn't 
                    ' been isntantiated, do so now
                    If Me.ClientMethodInfoList Is Nothing Then
                        Me.ClientMethodInfoList = New System.Collections.Generic.List(Of System.Reflection.MethodInfo)()
                    End If

                    ' Add the method to the list
                    Me.ClientMethodInfoList.Add(clientMethodInfo)

                    ' Generate JavaScript to expose the method on the client-side task.  The method will
                    ' have the same signature as that specified in its server-side declaration and, when
                    ' called, will fire a callback that automatically passes the client-side parameters
                    ' to the equivalent server-side method.
                    Dim addClientMethodScript As String = CreateAddClientMethodScript(clientMethodInfo)
                    clientMemberDeclarationScript.Append(addClientMethodScript)

                    ' Generate JavaScript to add eventing for server-side method calls to the client-side 
                    ' task.  This will allow the specification of handlers that execute when the method 
                    ' has fired on the server and program flow returns to the client.  Handlers can be 
                    ' added via <task>.add_on<methodName>Complete and removed via 
                    ' <task>.remove_on<methodName>Complete.
                    Dim addClientMethodEventScript As String = CreateAddMethodEventScript(clientMethodInfo)
                    clientMemberDeclarationScript.Append(addClientMethodEventScript)
                End If
            Next memberInfo

            Return clientMemberDeclarationScript.ToString()
        End Function

        ''' <summary>
        ''' Generates JavaScript to add the control specified to the client-side task
        ''' </summary>
        ''' <param name="controlMemberInfo">Contains information about the control to expose</param>
        Private Function CreateAddClientControlScript(ByVal controlMemberInfo As System.Reflection.FieldInfo) As String
            ' Instantiate a StringBuilder to store the JavaScript
            Dim addClientControlScript As New System.Text.StringBuilder()

            ' Get the control instance
            Dim control As System.Web.UI.Control = TryCast(controlMemberInfo.GetValue(Me), System.Web.UI.Control)

            ' Create JavaScript to create a property on the client-side task that refers to the
            ' DOM element corresponding to the passed-in control and has the same name as the
            ' server-side field (instance variable) storing the control.
            Dim id As String = Nothing
            Try
                id = control.ID.ToString()
            Catch e1 As Exception
                control.ID = controlMemberInfo.Name
            End Try

            Dim s As String = String.Format("{0}.prototype.get_{1} = function()" & "{{ return $get(this.get_id() + '_{2}'); }};" & Constants.vbLf, Me.ClientClassName, controlMemberInfo.Name, control.ID)
            addClientControlScript.AppendFormat("{0}.prototype.get_{1} = function()" & "{{ return $get(this.get_id() + '_{2}'); }};" & Constants.vbLf, Me.ClientClassName, controlMemberInfo.Name, control.ID)

            Return addClientControlScript.ToString()
        End Function

        ''' <summary>
        ''' Generates JavaScript to add the method specified to the client-side task.  When called, this
        ''' method will invoke the server-side method of the same name.
        ''' </summary>
        ''' <param name="clientMethodInfo">Contains information about the method to expose</param>
        Private Function CreateAddClientMethodScript(ByVal clientMethodInfo As System.Reflection.MethodInfo) As String
            ' Instantiate StringBuilder to store the JavaScript
            Dim addClientMethodScript As New System.Text.StringBuilder()

            ' Declare a function on the client-side task that has the same name as that passed-in
            addClientMethodScript.AppendFormat("{0}.prototype.{1} = function(", Me.ClientClassName, clientMethodInfo.Name)

            ' Iterate through the method's parameters, adding the name of each to the function signature,
            ' separated by commas
            For Each parameterInfo As System.Reflection.ParameterInfo In clientMethodInfo.GetParameters()
                addClientMethodScript.Append(parameterInfo.Name & ", ")
            Next parameterInfo

            ' Remove the last space and comma if the method has parameters
            If clientMethodInfo.GetParameters().Length > 0 Then
                addClientMethodScript.Remove(addClientMethodScript.Length - 2, 2)
            End If

            ' Close the function signature and add the opening curly brace
            addClientMethodScript.Append(") {" & Constants.vbLf)

            ' Add the beginning of a setTimeout statement.  Note that we put the callback invocation in
            ' a timeout with a delay of 0 to ensure proper execution of nested callbacks.  Otherwise,
            ' callbacks that are invoked via ScriptTask ClientMembers before a previous callback has 
            ' finished processing on the client would throw an error.
            addClientMethodScript.Append("window.setTimeout(String.format(""var context = '{0}';"", this.get_id()) + ")

            ' Declare the argument variable and start setting its value.  The string will be in key-value
            ' pairs in the format <key1>=<value1>&<key2>=<value2>&...  The string is left incomplete here
            ' because the names of the method parameters need to be added to it
            addClientMethodScript.AppendFormat("""var argument = 'EventArg=InvokeMethod&MethodName={0}" & "&Parameters="" + ", clientMethodInfo.Name)

            ' Iterate through the method's parameters, adding the name of each to the argument string.
            ' Note that these are not placed in quotes so they are evaluated at run-time.  Each is 
            ' connected with " + \"|\" +" so that, at run-time, the result is a string with the value
            ' of each parameter separated by a vertical line.
            For Each parameterInfo As System.Reflection.ParameterInfo In clientMethodInfo.GetParameters()
                addClientMethodScript.Append(parameterInfo.Name & " + ""|"" + ")
            Next parameterInfo

            ' If the method has parameters, remove the last separator.  And add a closing double quote.
            ' Otherwise, remove the plus sign.
            If clientMethodInfo.GetParameters().Length > 0 Then
                addClientMethodScript.Remove(addClientMethodScript.Length - 9, 9)
                addClientMethodScript.Append("+ """)
            Else
                addClientMethodScript.Remove(addClientMethodScript.Length - 4, 4)
            End If

            addClientMethodScript.Append("';")

            ' Add an invocation of the task's callback function string.  This will pass the argument 
            ' variable to the server-side ScriptTask's GetCallbackResult function, where the 
            ' server-side method specified by the MethodName parameter will be invoked.  Note that, since
            ' the statement is being passed to a timeout, we need to add an extra pair of backslashes so
            ' the quotes surrounding the callback string are escaped properly at run-time.

            addClientMethodScript.Append("eval(\"""" + this.get_callbackFunctionString() + ""\"");"", 0);" & Constants.vbLf)
            'addClientMethodScript.AppendFormat("eval(\\\"{0}\\\");\", 0);\n", this.CallbackFunctionString);


            ' Add the function's closing curly brace
            addClientMethodScript.Append("};" & Constants.vbLf)

            Return addClientMethodScript.ToString()
        End Function

        ''' <summary>
        ''' Generates JavaScript to add eventing for the method specified to the client-side task.  This
        ''' allows for easily specifying logic to execute after a server-side method has been invoked 
        ''' and program flow has returned to the client.
        ''' </summary>
        ''' <param name="clientMethodInfo">Contains information about the method to expose</param>
        Private Function CreateAddMethodEventScript(ByVal clientMethodInfo As System.Reflection.MethodInfo) As String
            ' Create JavaScript that will add event-related methods to the task
            Dim addMethodInvocationEventScript As String = "" & ControlChars.CrLf & "                // Adds a handler that will fire after the {1} method has executed on the server" & ControlChars.CrLf & "                {0}.prototype.add_on{1}Complete = function(handler, before) {{" & ControlChars.CrLf & "                    if (!this._on{1}CompleteHandlers)" & ControlChars.CrLf & "                        this._on{1}CompleteHandlers = new Array();" & ControlChars.CrLf & ControlChars.CrLf & "                    if (before)" & ControlChars.CrLf & "                        this._on{1}CompleteHandlers.splice(0, 0, handler);" & ControlChars.CrLf & "                    else" & ControlChars.CrLf & "                        this._on{1}CompleteHandlers.push(handler);" & ControlChars.CrLf & "                }};" & ControlChars.CrLf & ControlChars.CrLf & "                // Removes the passed-in handler" & ControlChars.CrLf & "                {0}.prototype.remove_on{1}Complete = function(handler) {{" & ControlChars.CrLf & "                    for (var i = 0; i < this._on{1}CompleteHandlers.length; i++)" & ControlChars.CrLf & "                    {{" & ControlChars.CrLf & "                        if (this._on{1}CompleteHandlers[i] == handler)" & ControlChars.CrLf & "                        {{" & ControlChars.CrLf & "                            this._on{1}CompleteHandlers.splice(i, 1);" & ControlChars.CrLf & "                            break;" & ControlChars.CrLf & "                        }}" & ControlChars.CrLf & "                    }}" & ControlChars.CrLf & "                }};" & ControlChars.CrLf & ControlChars.CrLf & "                // Called by ScriptTask after the {1} method has executed on the server.  Fires" & ControlChars.CrLf & "                // all handlers added via add_on{1}Complete." & ControlChars.CrLf & "                {0}.prototype._on{1}Complete = function(result)" & ControlChars.CrLf & "                {{" & ControlChars.CrLf & "                    // Iterate through the task's handlers array for the on{1}Complete event" & ControlChars.CrLf & "                    for (var i = 0; i < this._on{1}CompleteHandlers.length; i++)" & ControlChars.CrLf & "                    {{" & ControlChars.CrLf & "                        // Assign the current handler to a temporary property on the task.  We do " & ControlChars.CrLf & "                        // this so that, when the handler is called, the task is the caller.  " & ControlChars.CrLf & "                        // Therefore, references to ""this"" in a handler will refer to the task." & ControlChars.CrLf & "                        this._temp = this._on{1}CompleteHandlers[i];" & ControlChars.CrLf & ControlChars.CrLf & "                        // Execute the handler and check to see whether it returns false. If false " & ControlChars.CrLf & "                        // is returned, exit the loop, skipping execution of any other handlers." & ControlChars.CrLf & "                        if (this._temp(result) == false)" & ControlChars.CrLf & "                            break;" & ControlChars.CrLf & "                    }}" & ControlChars.CrLf & "                    " & ControlChars.CrLf & "                    this._temp = 'undefined';" & ControlChars.CrLf & "                }};"

            ' Substitute the client-side task name and method name into the script and return it
            addMethodInvocationEventScript = String.Format(addMethodInvocationEventScript, Me.ClientClassName, clientMethodInfo.Name)
            Return addMethodInvocationEventScript
        End Function

#End Region

#End Region

#Region "Custom Attributes - ClientMember, ClientTaskDefinitionExplicit, ClientTaskJavaScriptResource, ClientTaskClassName"

        ''' <summary>
        ''' Custom attribute specifying that the associated member that should be made available on the
        ''' task's client-side representation.  Can only be declared on methods and fields (instance
        ''' members) that are controls.
        ''' </summary>
        <System.AttributeUsage(System.AttributeTargets.Method Or System.AttributeTargets.Field, AllowMultiple:=False)> _
        Public Class ClientMember
            Inherits System.Attribute
        End Class

        ''' <summary>
        ''' Custom attribute specifying that the client task has been explicitly declared in the 
        ''' ScriptTask implementation.  When neither this attribute nor ClientTaskJavaScriptResource 
        ''' are present, the client task is declared automatically.  Only valid for classes.
        ''' </summary>
        <System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple:=False)> _
        Public Class ClientTaskDefinitionExplicit
            Inherits System.Attribute
        End Class

        ''' <summary>
        ''' Custom attribute specifying the fully qualified name of the JavaScript file containing 
        ''' the client task declaration.  When this attribute and ClientTaskDefinitionExplicit are 
        ''' not present, the client task is declared automatically.  Only valid for classes.
        ''' </summary>
        <System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple:=False)> _
        Public Class ClientTaskJavaScriptResource
            Inherits System.Attribute
            Private _resourceName As String

            Public Sub New(ByVal resourceName As String)
                _resourceName = resourceName
            End Sub

            Public ReadOnly Property ResourceName() As String
                Get
                    Return _resourceName
                End Get
            End Property
        End Class

        ''' <summary>
        ''' Custom attribute specifying the name of the client task class.  If not declared, the
        ''' client task class name will automatically match that of the server-side task.
        ''' </summary>
        <System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple:=False)> _
        Public Class ClientTaskClassName
            Inherits System.Attribute
            Private _className As String

            Public Sub New(ByVal className As String)
                _className = className
            End Sub

            Public ReadOnly Property ClassName() As String
                Get
                    Return _className
                End Get
            End Property
        End Class

#End Region
    End Class
End Namespace