ArcGIS Add dynamic data
ArcGIS_AddDynamicData_VBNet\App_Code\ServerObjectStateModifier.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
Public Class ServerObjectStateModifier
  ' Custom enumeration for specifying the behavior of ApplySimpleRenderers
  Public Enum RendererAction
    ApplyNew
    ApplyOriginal
    ApplyLast
  End Enum

  ' Adds the canada shapefile to the passed-in resource and assigns it the passed-in name
  Public Sub AddLayer(ByVal mapResourceLocal As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal, ByVal layerName As String)
    ' Get the map underlying the passed-in resource
    Dim mapServerObjects As ESRI.ArcGIS.Carto.IMapServerObjects = CType(mapResourceLocal.MapServer, ESRI.ArcGIS.Carto.IMapServerObjects)
        Dim aoMap As ESRI.ArcGIS.Carto.IMap = mapServerObjects.Map(mapResourceLocal.DataFrame)

        ' Check whether the map already contains a layer with the passed-in name
        Dim layerAdded As Boolean = False
        Dim i As Integer = 0
        Do While i < aoMap.LayerCount
            If aoMap.Layer(i).Name = layerName Then
                layerAdded = True
                Exit Do
            End If
            i += 1
        Loop

        ' Only execute the logic to add the layer if a layer of the same name has not already been added
        If (Not layerAdded) Then
            ' Create a reference to the canada shapefile as an ArcObjects feature layer
            Dim serverContext As ESRI.ArcGIS.Server.IServerContext = mapResourceLocal.ServerContextInfo.ServerContext
            Dim workspaceFactory As ESRI.ArcGIS.Geodatabase.IWorkspaceFactory = CType(serverContext.CreateObject("esriDataSourcesFile.ShapefileWorkspaceFactory"), ESRI.ArcGIS.Geodatabase.IWorkspaceFactory)
            Dim featureWorkspace As ESRI.ArcGIS.Geodatabase.IFeatureWorkspace = TryCast(workspaceFactory.OpenFromFile("C:\temp\data\", 0), ESRI.ArcGIS.Geodatabase.IFeatureWorkspace)
            Dim aoFeatureLayer As ESRI.ArcGIS.Carto.IFeatureLayer = CType(serverContext.CreateObject("esriCarto.FeatureLayer"), ESRI.ArcGIS.Carto.IFeatureLayer)
            aoFeatureLayer.FeatureClass = featureWorkspace.OpenFeatureClass(layerName)
            aoFeatureLayer.Name = layerName

            ' Apply a renderer to the feature layer
            Dim aoGeoFeatureLayer As ESRI.ArcGIS.Carto.IGeoFeatureLayer = TryCast(aoFeatureLayer, ESRI.ArcGIS.Carto.IGeoFeatureLayer)
            ApplySimpleRenderer(aoGeoFeatureLayer, serverContext, 0, 0, 210)

            ' Set the dynamic layer's visibility based on the session variable.  Default to visible if the session variable
            ' does not yet exist.
            If (Not System.Web.HttpContext.Current.Session("dynamicLayerVisible") Is Nothing) Then
                aoFeatureLayer.Visible = CBool(System.Web.HttpContext.Current.Session("dynamicLayerVisible"))
            Else
                aoFeatureLayer.Visible = True
            End If

            ' Add the layer to the map and move it below any other layers
            aoMap.AddLayer(aoFeatureLayer)
            aoMap.MoveLayer(aoFeatureLayer, aoMap.LayerCount - 1)

            ' Before pushing the changes to the server, create a dictionary containing the current visibility of the map layers.
            ' We do this because updating the objects on the server (via RefreshServerObjects) also updates the map description
            ' referenced by the map resource with the service's updated default description, which includes default layer 
            ' visibility.
            Dim visibleLayers As System.Collections.Generic.Dictionary(Of String, Boolean) = New System.Collections.Generic.Dictionary(Of String, Boolean)()
            For Each layerDescription As ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription In mapResourceLocal.MapDescription.LayerDescriptions
                visibleLayers.Add(layerDescription.LayerID.ToString(), layerDescription.Visible)
            Next layerDescription

            ' Register changes to server object - dynamic layer added.
            mapResourceLocal.RefreshServerObjects()

            ' Reset the visibility of the layers in the resource's map description to what it was before updating the map service.
            For Each layerVisiblePair As System.Collections.Generic.KeyValuePair(Of String, Boolean) In visibleLayers
                ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality.UpdateVisibleLayer(mapResourceLocal.MapDescription, layerVisiblePair.Key, layerVisiblePair.Value)
            Next layerVisiblePair
        End If
    End Sub

    ' Adds the passed-in GeoFeatureLayer to the passed-in local map resource and sets its position to that specified by the
    ' passed-in argument
    Public Sub AddLayer(ByVal mapResourceLocal As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal, ByVal geoFeatureLayer As ESRI.ArcGIS.Carto.IGeoFeatureLayer, ByVal layerIndex As Integer)
        ' Get the map underlying the passed-in resource
        Dim mapServerObjects As ESRI.ArcGIS.Carto.IMapServerObjects = CType(mapResourceLocal.MapServer, ESRI.ArcGIS.Carto.IMapServerObjects)
        Dim aoMap As ESRI.ArcGIS.Carto.IMap = mapServerObjects.Map(mapResourceLocal.DataFrame)

        ' Get the server context of the passed-in map service
        Dim serverContext As ESRI.ArcGIS.Server.IServerContext = mapResourceLocal.ServerContextInfo.ServerContext

        ' Add the passed-in layer to the map and move it to the passed-in index
        aoMap.AddLayer(geoFeatureLayer)
        aoMap.MoveLayer(geoFeatureLayer, layerIndex)

        ' Before pushing the changes to the server, create a dictionary containing the current visibility of the map layers.
        ' We do this because updating the objects on the server (via RefreshServerObjects) also updates the map description
        ' referenced by the map resource with the service's updated default description, which includes default layer 
        ' visibility.  So when RefreshServerObjects is called, the visibility of the map layers will be reset to default
        Dim visibleLayers As System.Collections.Generic.Dictionary(Of String, Boolean) = New System.Collections.Generic.Dictionary(Of String, Boolean)()
        For Each layerDescription As ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription In mapResourceLocal.MapDescription.LayerDescriptions
            visibleLayers.Add(layerDescription.LayerID.ToString(), layerDescription.Visible)
        Next layerDescription

        ' Register changes to server object, adding the passed-in layer at the map service level.
        mapResourceLocal.RefreshServerObjects()

        ' Reset the visibility of the layers in the resource's map description to what it was before updating the map service.
        For Each layerVisiblePair As System.Collections.Generic.KeyValuePair(Of String, Boolean) In visibleLayers
            ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality.UpdateVisibleLayer(mapResourceLocal.MapDescription, layerVisiblePair.Key, layerVisiblePair.Value)
        Next layerVisiblePair
    End Sub

    ' Removes the layer with the passed-in name from the passed-in resource
    Public Function RemoveLayer(ByVal mapResourceLocal As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal, ByVal layerName As String) As System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.ILayer)
        ' Get the map underlying the passed-in map resource
        Dim mapServerObjects As ESRI.ArcGIS.Carto.IMapServerObjects = CType(mapResourceLocal.MapServer, ESRI.ArcGIS.Carto.IMapServerObjects)
        Dim aoMap As ESRI.ArcGIS.Carto.IMap = mapServerObjects.Map(mapResourceLocal.DataFrame)

        ' Get a reference to the layer with the passed-in name from the map
        Dim enumLayer As ESRI.ArcGIS.Carto.IEnumLayer = aoMap.Layers(Nothing, True)
        Dim currentLayer As ESRI.ArcGIS.Carto.ILayer = enumLayer.Next()
        Dim removedLayerDictionary As System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.ILayer) = New System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.ILayer)()
        Dim layerIndex As Integer = 0

        Do While currentLayer IsNot Nothing
            If currentLayer.Name = layerName Then
                removedLayerDictionary.Add(layerIndex, currentLayer)
                Exit Do
            End If
            currentLayer = enumLayer.Next()
            layerIndex += 1
        Loop

        ' Make sure the layer was found before executing logic to remove it
        If Not currentLayer Is Nothing Then
            ' Delete the layer from the map
            aoMap.DeleteLayer(currentLayer)

            ' Before committing the change to the map service, create a dictionary containing the current visibility of the map layers.
            ' We do this because updating the objects on the server (via RefreshServerObjects) also updates the map description
            ' referenced by the map resource with the service's updated default description, which includes default layer visibility.
            Dim visibleLayers As System.Collections.Generic.Dictionary(Of String, Boolean) = New System.Collections.Generic.Dictionary(Of String, Boolean)()
            For Each layerDescription As ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription In mapResourceLocal.MapDescription.LayerDescriptions
                visibleLayers.Add(layerDescription.LayerID.ToString(), layerDescription.Visible)
            Next layerDescription

            ' Register the layer removal with the server object
            mapResourceLocal.RefreshServerObjects()

            ' Reset the visibility of the remaining layers back to what it was before the update
            For Each layerVisiblePair As System.Collections.Generic.KeyValuePair(Of String, Boolean) In visibleLayers
                ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality.UpdateVisibleLayer(mapResourceLocal.MapDescription, layerVisiblePair.Key, layerVisiblePair.Value)
            Next layerVisiblePair
        End If

        Return removedLayerDictionary
    End Function

    ' Moves the layer with the passed-in name to the passed-in index within the passed-in resource
    Public Function MoveLayer(ByVal mapResourceLocal As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal, ByVal layerName As String, ByVal layerIndex As Integer) As Integer
        ' Get the map underlying the passed-in map resource
        Dim mapServerObjects As ESRI.ArcGIS.Carto.IMapServerObjects = CType(mapResourceLocal.MapServer, ESRI.ArcGIS.Carto.IMapServerObjects)
        Dim aoMap As ESRI.ArcGIS.Carto.IMap = mapServerObjects.Map(mapResourceLocal.DataFrame)

        If (layerIndex < 0) OrElse (layerIndex > aoMap.LayerCount - 1) Then
            Return -1
        End If

        ' Get a reference to the layer with the passed-in name from the map
        Dim enumLayer As ESRI.ArcGIS.Carto.IEnumLayer = aoMap.Layers(Nothing, True)
        Dim currentLayer As ESRI.ArcGIS.Carto.ILayer = enumLayer.Next()

        Do While currentLayer IsNot Nothing
            If currentLayer.Name = layerName Then
                Exit Do
            End If
            currentLayer = enumLayer.Next()
        Loop

        ' Make sure the layer was found before executing logic to move it
        If Not currentLayer Is Nothing Then
            ' Move the layer to the passed-in layer index
            aoMap.MoveLayer(currentLayer, layerIndex)

            ' Before committing the change to the map service, create a dictionary containing the current visibility of the map layers.
            ' We do this because updating the objects on the server (via RefreshServerObjects) also updates the map description
            ' referenced by the map resource with the service's updated default description, which includes default layer visibility.
            Dim visibleLayers As System.Collections.Generic.Dictionary(Of String, Boolean) = New System.Collections.Generic.Dictionary(Of String, Boolean)()
            For Each layerDescription As ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription In mapResourceLocal.MapDescription.LayerDescriptions
                visibleLayers.Add(layerDescription.LayerID.ToString(), layerDescription.Visible)
            Next layerDescription

            ' Register the layer repositioning with the server object
            mapResourceLocal.RefreshServerObjects()

            ' Reset the visibility of the remaining layers back to what it was before the update
            For Each layerVisiblePair As System.Collections.Generic.KeyValuePair(Of String, Boolean) In visibleLayers
                ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality.UpdateVisibleLayer(mapResourceLocal.MapDescription, layerVisiblePair.Key, layerVisiblePair.Value)
            Next layerVisiblePair
        End If

        Return layerIndex
    End Function

    ' Changes the renderers for all layers belonging to the passed-in local map resource.  If rendererAction is ApplyNew, 
    ' the renderers are set to simple renderers with randomly generated colors.  If ApplyOriginal, the renderers are set 
    ' to what they were when the page first loaded.  If ApplyLast, then the renderers are set to the last new renderer
    ' applied.
    Public Sub ApplySimpleRenderers(ByVal mapResourceLocal As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal, ByVal rendererAction As RendererAction)
        ' Get a reference to the ArcObjects map and layers via server context
        Dim aoServerContext As ESRI.ArcGIS.Server.IServerContext = mapResourceLocal.ServerContextInfo.ServerContext
        Dim aoMapServerObjects As ESRI.ArcGIS.Carto.IMapServerObjects = CType(mapResourceLocal.MapServer, ESRI.ArcGIS.Carto.IMapServerObjects)
        Dim aoMap As ESRI.ArcGIS.Carto.IMap = aoMapServerObjects.Map(mapResourceLocal.DataFrame)
        Dim enumLayer As ESRI.ArcGIS.Carto.IEnumLayer = aoMap.Layers(Nothing, True)

        ' For each layer, we will store a dictionary containing its initial renderers (i.e. on initial load) in session.
        ' Check whether the session variable for the current layer has already been created.  If so, retrieve it.  If not, 
        ' create a new dictionary.  This dictionary will be used to reset renderers to their initial load state. 
        Dim originalRendererDictionary As System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.IFeatureRenderer) = Nothing
        Dim originalRendererKey As String = String.Format("{0}_originalRendererDictionary", mapResourceLocal.Name)
        If System.Web.HttpContext.Current.Session(originalRendererKey) Is Nothing Then
            originalRendererDictionary = New System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.IFeatureRenderer)()
        Else
            originalRendererDictionary = CType(System.Web.HttpContext.Current.Session(originalRendererKey), System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.IFeatureRenderer))
        End If

        ' For each layer, we will also store a dictionary containing its current renderers.  This will be used to re-apply 
        ' the current renderer on each page request.
        Dim currentRendererDictionary As System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.IFeatureRenderer) = Nothing
        Dim currentRendererKey As String = String.Format("{0}_currentRendererDictionary", mapResourceLocal.Name)
        If System.Web.HttpContext.Current.Session(currentRendererKey) Is Nothing Then
            currentRendererDictionary = New System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.IFeatureRenderer)()
        Else
            currentRendererDictionary = CType(System.Web.HttpContext.Current.Session(currentRendererKey), System.Collections.Generic.Dictionary(Of Integer, ESRI.ArcGIS.Carto.IFeatureRenderer))
        End If

        ' Loop through each layer in the current map, either applying a new random simple renderer, reverting to the random simple
        ' renderer that was last applied, or reverting the renderer to its initial load state.
        Dim layerIndex As Integer = 0
        Dim aoGeoFeatureLayer As ESRI.ArcGIS.Carto.IGeoFeatureLayer = CType(enumLayer.Next(), ESRI.ArcGIS.Carto.IGeoFeatureLayer)

        Do While aoGeoFeatureLayer IsNot Nothing
            Select Case rendererAction
                Case rendererAction.ApplyNew
                    ' If the current layer's renderer is not in the dictionary of initial renderers, then this is the
                    ' first time this code has been executed during the session.  The layer's current renderer is 
                    ' therefore the initial load state renderer, so we add it to the original renderer dictionary.
                    If (Not originalRendererDictionary.ContainsKey(layerIndex)) Then
                        originalRendererDictionary.Add(layerIndex, aoGeoFeatureLayer.Renderer)
                        System.Web.HttpContext.Current.Session(originalRendererKey) = originalRendererDictionary
                    End If

                    ' Apply a randomly colored simple renderer to the current layer
                    ApplySimpleRenderer(aoGeoFeatureLayer, aoServerContext, -1, -1, -1)

                    ' If the current renderer dictionary contains the current layer's renderer, remove it so it can
                    ' be replaced with the new renderer.
                    If currentRendererDictionary.ContainsKey(layerIndex) Then
                        currentRendererDictionary.Remove(layerIndex)
                    End If

                    ' Update the current renderer dictionary with the current renderer
                    currentRendererDictionary.Add(layerIndex, aoGeoFeatureLayer.Renderer)
                    System.Web.HttpContext.Current.Session(currentRendererKey) = currentRendererDictionary
                Case rendererAction.ApplyLast
                    ' Set the layer's renderer to the last random simple renderer applied
                    aoGeoFeatureLayer.Renderer = CType(currentRendererDictionary(layerIndex), ESRI.ArcGIS.Carto.IFeatureRenderer)
                Case rendererAction.ApplyOriginal
                    ' Set the layer's renderer to the one it had when the session started
                    If originalRendererDictionary.ContainsKey(layerIndex) Then
                        aoGeoFeatureLayer.Renderer = CType(originalRendererDictionary(layerIndex), ESRI.ArcGIS.Carto.IFeatureRenderer)
                    End If
            End Select
            aoGeoFeatureLayer = CType(enumLayer.Next(), ESRI.ArcGIS.Carto.IGeoFeatureLayer)
            layerIndex += 1
        Loop

        ' Get the current resource's map description before the map and layers are updated on the server.  We do this
        ' because when the server objects are updated, the layer visibility reverts to that specified by the map 
        ' document underlying the map service, and the map description contains information about current layer 
        ' visibility
        Dim adfMapDescription As ESRI.ArcGIS.ADF.ArcGISServer.MapDescription = mapResourceLocal.MapDescription

        ' Register the layer changes with the server.  This applies the new renderer.
        mapResourceLocal.RefreshServerObjects()

        ' Set the resource's map description to that retrieved before server objects were updated, so layer visibility
        ' is the same as before the update.
        mapResourceLocal.MapDescription = adfMapDescription
    End Sub

  ' Generates a simple renderer and applies it to the passed-in layer.  For any passed-in color value that is not within the
  ' valid range (0-255), a random value within the valid range is applied.  So, for example, a randomly colored renderer can 
  ' be generated by passing in RGB parameters of -1, -1, and -1.
  Private Sub ApplySimpleRenderer(ByVal aoGeoFeatureLayer As ESRI.ArcGIS.Carto.IGeoFeatureLayer, ByVal aoServerContext As ESRI.ArcGIS.Server.IServerContext, ByVal red As Integer, ByVal green As Integer, ByVal blue As Integer)
    ' Generate an ArcObjects color for the renderer.  Use the specified RGB value if valid, otherwise use a random integer within
    ' the valid range.
    Dim aoRgbColor As ESRI.ArcGIS.Display.IRgbColor = CType(aoServerContext.CreateObject("esriDisplay.RgbColor"), ESRI.ArcGIS.Display.IRgbColor)
    Dim randomizer As System.Random = New System.Random()
    If (red < 0) OrElse (red > 255) Then
      aoRgbColor.Red = randomizer.Next(0, 256)
    Else
      aoRgbColor.Red = red
    End If

    If (green < 0) OrElse (green > 255) Then
      aoRgbColor.Green = randomizer.Next(0, 256)
    Else
      aoRgbColor.Green = green
    End If

    If (blue < 0) OrElse (blue > 255) Then
      aoRgbColor.Blue = randomizer.Next(0, 256)
    Else
      aoRgbColor.Blue = blue
    End If

    ' Instantiate an ArcObjects simple renderer to use in specifying the layer's renderer
    Dim aoSimpleRenderer As ESRI.ArcGIS.Carto.ISimpleRenderer = CType(aoServerContext.CreateObject("esriCarto.SimpleRenderer"), ESRI.ArcGIS.Carto.ISimpleRenderer)

    ' Check the layer's geometry type and create a symbol for the renderer accordingly
    Dim geometryType As ESRI.ArcGIS.Geometry.esriGeometryType = aoGeoFeatureLayer.FeatureClass.ShapeType
    Select Case aoGeoFeatureLayer.FeatureClass.ShapeType
      Case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPoint
        Dim aoSimpleMarkerSymbol As ESRI.ArcGIS.Display.ISimpleMarkerSymbol = CType(aoServerContext.CreateObject("esriDisplay.SimpleMarkerSymbol"), ESRI.ArcGIS.Display.ISimpleMarkerSymbol)
        aoSimpleMarkerSymbol.Color = CType(aoRgbColor, ESRI.ArcGIS.Display.IColor)
        aoSimpleRenderer.Symbol = CType(aoSimpleMarkerSymbol, ESRI.ArcGIS.Display.ISymbol)
      Case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPolyline
        Dim aoSimpleLineSymbol As ESRI.ArcGIS.Display.ISimpleLineSymbol = CType(aoServerContext.CreateObject("esriDisplay.SimpleLineSymbol"), ESRI.ArcGIS.Display.ISimpleLineSymbol)
        aoSimpleLineSymbol.Color = CType(aoRgbColor, ESRI.ArcGIS.Display.IColor)
        aoSimpleRenderer.Symbol = CType(aoSimpleLineSymbol, ESRI.ArcGIS.Display.ISymbol)
      Case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPolygon
        Dim aoSimpleFillSymbol As ESRI.ArcGIS.Display.ISimpleFillSymbol = CType(aoServerContext.CreateObject("esriDisplay.SimpleFillSymbol"), ESRI.ArcGIS.Display.ISimpleFillSymbol)
        aoSimpleFillSymbol.Color = CType(aoRgbColor, ESRI.ArcGIS.Display.IColor)
        aoSimpleRenderer.Symbol = CType(aoSimpleFillSymbol, ESRI.ArcGIS.Display.ISymbol)
      Case Else
        Throw New System.Exception("No renderer or symbol selected.  Shape type undetermined.")
    End Select

    ' Assign the new renderer to the passed-in layer
    aoGeoFeatureLayer.Renderer = CType(aoSimpleRenderer, ESRI.ArcGIS.Carto.IFeatureRenderer)
  End Sub
End Class