Adding a FeatureLayer by Name

Feature layers are immensely useful and powerful types that can be used within your Esri web applications. At a basic level they can be thought of as a GraphicsLayer with an associated QueryTask. The usual way of adding a FeatureLayer would be to reference the url of the layer you want to use. This url is composed of the map service endpoint and the layer id e.g.http://server/ArcGIS/rest/services/name/MapServer/0 where 0 is the layer id.

What we will look at now is how we can add a FeatureLayer by referencing the layer name instead of the id. The reason that this would be useful is that this would protect your layer from failing to load if the underlying map document that has been published as a service has had its layer order changed as this would mean that the layer id would change too.

To achieve this functionality we can query the REST endpoint of the service, lookup the service information and then use this to determine the layer id that corresponds to the supplied layer name. This technique can be used to retrieve all of the map service information but here I will just be returning the layer id and name.

Here’s the code to create the FeatureLayer

public static class FeatureLayerExtensions
{
    public static void FromLayerNameEndpoint(string mapServiceLayerNameEndpoint,
                                                Action<ESRI.ArcGIS.Client.FeatureLayer> onLayerCreated)
    {
        var featureLayer = new ESRI.ArcGIS.Client.FeatureLayer();

        featureLayer.GetLayerId(mapServiceLayerNameEndpoint,
                                (fLayer, url) =>
                                {
                                    fLayer.Url = url;
                                    if (onLayerCreated != null) onLayerCreated(fLayer);
                                });
    }

    public static void GetLayerId(this ESRI.ArcGIS.Client.FeatureLayer featureLayer,
                                    string endpoint,
                                    Action<ESRI.ArcGIS.Client.FeatureLayer, string> onLayerIdFound)
    {
        featureLayer.GetLayerId(endpoint, string.Empty, onLayerIdFound);
    }

    public static void GetLayerId(this ESRI.ArcGIS.Client.FeatureLayer featureLayer,
                                    string endpoint,
                                    string proxyUrl,
                                    Action<ESRI.ArcGIS.Client.FeatureLayer, string> onLayerIdFound)
    {
        var layerName = endpoint.TrimEnd('/').Split('/').Last();
        var uri = endpoint.TrimEnd('/').Substring(0, endpoint.TrimEnd('/').LastIndexOf('/'));

        var request = new WebClient();
        request.OpenReadCompleted += (sender, e) =>
        {
            // Using the built in Json serializer but you could use something else e.g. Json.NET
            var s = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(LayerInfo));
            var layers = ((LayerInfo)s.ReadObject(e.Result)).Layers;

            foreach (var layer in layers)
            {
                if (string.Equals((string)layer.Name, layerName, StringComparison.OrdinalIgnoreCase))
                {
                    if (onLayerIdFound != null) onLayerIdFound(featureLayer, string.Format("{0}/{1}", uri, layer.Id));
                    return;
                }
            }

            throw new InvalidOperationException(string.Format("A layer with the name '{0}' was not found in the map service.", layerName));
        };

        if (string.IsNullOrEmpty(proxyUrl))
            request.OpenReadAsync(new Uri(EnsureJsonResponseFormatUrl(uri)));
        else
            request.OpenReadAsync(new Uri(string.Format("{0}?{1}", proxyUrl, EnsureJsonResponseFormatUrl(uri))));
    }
        
    static string EnsureJsonResponseFormatUrl(string url)
    {
        if (url == null) throw new ArgumentNullException("url");

        if (url.IndexOf("f=json") == -1)
        {
            if (url.IndexOf('?') == -1)
                url += "?";
            else
                url += "&";
            return url + "f=json";
        }
        else
            return url;
    }
}

and the data contract that is returned from the endpoint

[DataContract]
public class LayerInfo
{
    [DataMember(Name = "layers")]
    public LayerInfoItem[] Layers { get; set; }

}

/// <summary>
/// Contains layer properties from the MapServer REST endpoint.
/// Could be extended to return all values available e.g. type, minScale etc. 
/// </summary>
[DataContract]
public class LayerInfoItem
{
    [DataMember(Name = "id")]
    public int Id { get; set; }

    [DataMember(Name = "name")]
    public string Name { get; set; }
}

and how to call it

var url = "http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Earthquakes/EarthquakesFromLastSevenDays/MapServer/Earthquakes from last 7 days";
FeatureLayerExtensions.FromLayerNameEndpoint(url,
                                                (featureLayer) =>
                                                {    
                                                    // TODO : any other initialization 
                                                    featureLayer.Opacity = 0.6;
                                                    MyMap.Layers.Add(featureLayer);
                                                });

Now when you run your application you will see the FeatureLayer added to your map. If you have fiddler running you can see the requests being made. I’d encourage you to dig into these to help understand the process involved.

A final point; A FeatureLayer cannot have its Url property set after the layer has been initialized. This is important here as it means that we need to create and add the layer in code rather than with markup.

Advertisements

6 comments

  1. Dave, I would like to extend this concept. It would be very cool to allow the user to add a /MapServer endpoint to the map, and do discovery on the layers to find out if any are editable, then add featurelayers with selection-only mode, with the net result that only the dynamic map service is displayed, but the user can click and edit features as if it were a group of featurelayers. Seems like the tricky part is discovering editability. Anyway just an idea for a future blog post. I really enjoy reading your blog. Thanks for sharing your ideas!

  2. I like this solution. Downside is that layer names do not need to be unique. We have clients that will create group layers based on a layer definition and each group layer contains the same n layers. Each layer in each group would have the same name.

    1. Yea that’s the problem. We try to distinguish multiple copies of the same data by some constraint e.g. scale dependency, but since there’s no guarantee that a duplicate name won’t exist the chance of someone authoring the map document like that is always present. This example could do an extra check on the layer type to make sure it is a Feature Layer so at least then it would ensure that the id is for a valid layer.

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