Dynamically Loading Layers at Runtime with Silverlight

If you’ve ever worked on a ArcGIS Server project that involves more than one environment then chances are you’ve encountered the scenario where your map service endpoints need to be changed when migrating from DEV to TEST and PROD and so on. There are a few ways to handle this such as hard coding the url’s and then recompiling for each environment (don’t do that Smile), storing the url’s in the web.config (ok, but it’s not that flexible if you want to add or remove layers) or you could store the config in a database and use a service to load the data. Each of these methods has it’s pros and cons but I’ve been using another method which is useful so let’s take a look.

First we need to create a new Silverlight project in Visual Studio and add the web project to host it. Add a reference for the latest ESRI.ArcGIS.Client then paste the following into the MainPage.xaml (be careful of carriage returns I had to insert for formatting).

<UserControl x:Class="Sample.DynamicLayerLoading.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:esri="clr-namespace:ESRI.ArcGIS.Client;assembly=ESRI.ArcGIS.Client"
  mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">

  <Grid x:Name="LayoutRoot" Background="White">
    <esri:Map WrapAround="True" x:Name="Map">
      <esri:ArcGISTiledMapServiceLayer ID="BaseMapLayer"
        Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer" />
    </esri:Map>
  </Grid>
</UserControl>

If you run the application you should see a map

image

Now add a file called Layers.xaml to the root of your web site. The solution structure should be

image

Within this file you can put any valid Xaml for map layers, just remember to add the correct namespace declaration and enclose the layers in a <esri:LayerCollection> tag. Mine contains

<esri:LayerCollection
  xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <esri:ArcGISDynamicMapServiceLayer ID="MyLayer"
    Url="http://maverick.arcgis.com/AWorld_WGS84/MapServer"
    VisibleLayers="1,2" Opacity="0.5"/>

  <esri:FeatureLayer ID="MyFeatureLayer"
    Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/0"
    Where="POP1990 > 100000" />
</esri:LayerCollection>

Lastly you need to paste the following into the MainPage.xaml.cs file

using System;
using System.Linq;
using System.Net;
using System.Windows.Browser;
using ESRI.ArcGIS.Client;

namespace Sample.DynamicLayerLoading
{
   public partial class MainPage
   {
       public MainPage()
       {
           InitializeComponent();
           Map.LoadLayersFromXaml(
               OnLoadLayersFromXamlComplete,
               HtmlPage.Document.DocumentUri.ToString()
                 .Substring(0, HtmlPage.Document.DocumentUri.ToString().LastIndexOf('/')),
               1);
       }

       private void OnLoadLayersFromXamlComplete()
       {
            // TODO : whatever you want after the layers are initialised 
       }
   }

   internal static class MapExtensions
   {
       /// <summary> 
       /// Loads the layer collection from the downloaded xaml file. 
       /// </summary> 
       /// <param name="map"></param> 
       /// <param name="onCompleted">The callback to execute once the method has finished processing</param> 
       /// <param name="absoluteUrlBase">The absolute url to the application root. Used to determine proxy url</param> 
       /// <param name="startingIndex">The index at which to start inserting the map layers</param> 
       public static void LoadLayersFromXaml(this Map map, Action onCompleted, string absoluteUrlBase, int startingIndex)
       {
           var client = new WebClient();
           client.DownloadStringCompleted += (sender, args) =>
           {
               // Parse xaml to a LayerCollection  
               string xaml = args.Result;
               var layerCollection = System.Windows.Markup.XamlReader.Load(xaml);
               var layers = (layerCollection as LayerCollection);

               if (layers != null)
                   for (var i = 0; i < layers.Count; i++)
                   {
                       if (layers[i] is ArcGISTiledMapServiceLayer && !string.IsNullOrWhiteSpace(((ArcGISTiledMapServiceLayer)layers[i]).ProxyURL))
                           ((ArcGISTiledMapServiceLayer)layers[i]).ProxyURL = string.Format("{0}{1}", absoluteUrlBase, ((ArcGISTiledMapServiceLayer)layers[i]).ProxyURL.Split('/').Last());
                       else if (layers[i] is ArcGISImageServiceLayer && !string.IsNullOrWhiteSpace(((ArcGISImageServiceLayer)layers[i]).ProxyURL))
                           ((ArcGISImageServiceLayer)layers[i]).ProxyURL = string.Format("{0}{1}", absoluteUrlBase, ((ArcGISImageServiceLayer)layers[i]).ProxyURL.Split('/').Last());
                       else if (layers[i] is ArcGISDynamicMapServiceLayer && !string.IsNullOrWhiteSpace(((ArcGISDynamicMapServiceLayer)layers[i]).ProxyURL))
                           ((ArcGISDynamicMapServiceLayer)layers[i]).ProxyURL = string.Format("{0}{1}", absoluteUrlBase, ((ArcGISDynamicMapServiceLayer)layers[i]).ProxyURL.Split('/').Last());
                        else if (layers[i] is FeatureLayer && !string.IsNullOrWhiteSpace(((FeatureLayer)layers[i]).ProxyUrl))
                            ((FeatureLayer)layers[i]).ProxyUrl = string.Format("{0}{1}", absoluteUrlBase, ((FeatureLayer)layers[i]).ProxyUrl.Split('/').Last());
                        map.Layers.Insert(i + startingIndex, layers[i]);
                    }
                onCompleted();
            };

            client.DownloadStringAsync(new Uri(string.Format("{0}/Layers.xaml", absoluteUrlBase), UriKind.Absolute));
       }
   }
}

Run the application again and now you will see

image

The layers are dynamically loaded at runtime allowing you to change them without needing to rebuild your xap file. One thing to note is that you do not want to use this technique to load a Bing Imagery service with the token specified as this would be available to anyone sniffing the downloaded Layers.xaml file.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s