Getting Attachments for a MapServer (not FeatureServer)

3599
16
04-19-2011 09:54 AM
TerryGiles
Occasional Contributor III
Hi All,

Looking through the samples and API docs, the only direct access to a feature's attachments is via FeatureLayer::QueryAttachmentInfos().  Is there, or will there be in v2.2 of the API, a similar method for a MapServer layer?  The only way I can see to do it now is through the service's REST end point.

The code below is an adaptation of the ESRI Identify sample which uses the REST serive to check for attachments and builds a series of hyperlink buttons to open them if so.  I can't really use the AttachmentEditor control for this since it a) requires a FeatureLayer and most of my services do not have this enabled, b) the Identify task returns results from multiple layers, and c) I don't want folks to be able to add or delete attachments.

Anyone know of another way to do this?

Thanks, 
Terry

private void IdentifyTask_ExecuteCompleted(Object sender, IdentifyEventArgs args)
        {

            if (this.Visibility == Visibility.Collapsed)
            {
                this.Visibility = Visibility.Visible;
            }
            
            IdentifyResultsPanel.Visibility = Visibility.Visible;
            AddResultstoControl(args.IdentifyResults, args.UserState);

        }

       private void AddResultstoControl(List<IdentifyResult> results, object token)
        {
            //token contains the map layer index 

            if (_dataItems == null)
            {
                _dataItems = new List<DataItem>();
            }


            if (_dataLinks == null)
            {
                _dataLinks = new Dictionary<string, List<DataLink>>();   
            }


            if ((results != null) && (results.Count > 0))
            {

                Graphic feature = null;
                string title = string.Empty;
                string oid = string.Empty;

                WebClient web = null;
                WebClient webAttach = null;

                string urlLayer = string.Empty;
                string urlFeature = string.Empty;
                
                bool hasOID = false;


                foreach (IdentifyResult result in results)
                {
                    web = new WebClient();
                    webAttach = new WebClient();

                    urlFeature = string.Empty;
                    hasOID = false;
                
                    feature = result.Feature;
                    title = result.Value.ToString() + " (" + result.LayerName + ")";

                    urlLayer = MapUtils.GetLayerURL(_Map.Layers[Convert.ToInt32(token.ToString())]) + "/" + result.LayerId;


                    foreach (KeyValuePair<string,object> kvp in result.Feature.Attributes)
                    {
                        switch (kvp.Key.ToLower())
                     {
                            case "objectid":
                            case "fid":

                                urlFeature = urlLayer +  "/" + kvp.Value.ToString();

                                _dataItems.Add(new DataItem() { Title = title, Data = feature.Attributes, Id = urlFeature });
                                _dataLinks.Add(urlFeature, new List<DataLink>() );

                                cbxIdentify.Items.Add(title);

                                urlFeature += "/attachments?f=json";
                                hasOID = true;
                                
                                break;
                      default:
                                break;
                     }
                        if (hasOID)
                        {
                            break;
                        }
                       
                    }

                    //see if layer hasAttachements; download them if so
                    web.DownloadStringCompleted += (s, e) =>
                    {
                        bool hasAttachments = false;
                        JsonObject jsObj = (JsonObject)JsonObject.Parse(e.Result);

                        if (jsObj.ContainsKey("hasAttachments"))
                        {
                            hasAttachments = Convert.ToBoolean(jsObj["hasAttachments"].ToString());
                        }
                        else
                        {
                            hasAttachments = false;
                        }
                        if (hasAttachments)
                        {
                            webAttach = new WebClient();
                            webAttach.DownloadStringCompleted += web_DownloadStringCompleted;
                            webAttach.DownloadStringAsync(new Uri(urlFeature), urlFeature);
                        }

                    };
                    web.DownloadStringAsync(new Uri(urlLayer + "?f=json"));


                }

                cbxIdentify.UpdateLayout();
                //cbxIdentify.SelectedIndex = 0;
            }
        }

        void web_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            //e will have the JSON of a features attachments
            string urlbase = e.UserState.ToString().Replace("/attachments?f=json", "");
            DataLink dl = null;

            JsonObject jsObj = (JsonObject)JsonObject.Parse(e.Result);

            if (jsObj.ContainsKey("error"))
            {
                return;
            }
            JsonArray AttachInfos = (JsonArray)jsObj["attachmentInfos"];

            foreach (JsonObject jOb in AttachInfos)
            {
                dl = new DataLink() { Caption = jOb["name"].ToString().Trim('"'), Link = new Uri(urlbase + "/attachments/" + jOb["id"].ToString()) };
                _dataLinks[urlbase].Add(dl);
            }


        }

       //class for data grid binding
        private class DataItem
        {
            private string _id;
            private string _Title;
            private IDictionary<string, Object> _Data;

            public string Id
            {
                get { return _id; }
                set {_id = value;}
            }

            public string Title
            {
                get { return _Title; }
                set { _Title = value; }
            }

            public IDictionary<string, Object> Data
            {
                get { return _Data; }
                set { _Data = value; }
            }
        }

        //for storing links to attachments 
        private class DataLink
        {
            private Uri _link;
            private string _caption;

            public Uri  Link
            {
                get { return _link; }
                set { _link = value; }
            }

            public string Caption
            {
                get { return _caption; }
                set { _caption = value;}
            }
        }


0 Kudos
16 Replies
MarkMa
by
New Contributor
Terry, Thank very much for reply . follow your instructions error message 2, 3, 4 solved. But I can not solve the number 1. You mensioned that you made the MapUtils Class. I have tried to construct the class but I can not make it work. Would you please teaching me for how to contruct the Class?

Here is the Class that I tried.

public class MapUtils
    {
         internal static string GetLayerURL(ESRI.ArcGIS.Client.Layer layer)
        {
                  
        }
    }

1.  "The name 'MapUtils' does not exist in the current context".  MapUtils is a utility class I made and the function GetLayerURL simply returns the URL of a layer in the map.

Thanks.
Mark
0 Kudos
TerryGiles
Occasional Contributor III
it's just an quick function to determine the type of the layer passed in and return the Url property, since there is not Url property on the ESRI.ArcGIS.Client.Layer class. 

       public static string GetLayerURL(Layer lyr)
        {
            string strURL = string.Empty;

            if (lyr is ArcGISDynamicMapServiceLayer)
            {
                strURL = (lyr as ArcGISDynamicMapServiceLayer).Url;
            }
            else if (lyr is ArcGISTiledMapServiceLayer)
            {
                strURL = (lyr as ArcGISTiledMapServiceLayer).Url;
            }
            else if (lyr is ArcGISImageServiceLayer)
            {
                strURL = (lyr as ArcGISImageServiceLayer).Url;
            }
            else if (lyr is FeatureLayer)
            {
                strURL = (lyr as FeatureLayer).Url;
            }

            return strURL;
        }
0 Kudos
MarkMa
by
New Contributor
Terry, Thank you very much for teching me to construct the MapUtils Class. After implement the class there is no error message. However, When I run the application and click a point the application point to error on
"urlLayer = MapUtils.GetLayerURL(MyMap.Layers[Convert.ToInt32(token.ToString())]) + "/" + result.LayerId;"
I setup debug and find that the string valuable "urlLayer" is empty and "token" is null.
But MyMap.Layers count is correct, result is displayed and LayerId is correct.
The Error message is "Object reference not set to an instance of an object"
I have tried to debug but I don't kown why urlLayer is empty and token is null.
but I belive that we are very close to solve the problem
I have attachment the C# code and xaml and screen in doc. Would you please teach where I may go wrong.
Thanks
Mark
0 Kudos
TerryGiles
Occasional Contributor III
Hi Mark,

When I run the Identify, it's called against several layers in my map, so I pass in the layer index as the token as well; something like this -
 //run ID on each service - starting from top so results more likely to match layer order in map
            for (int i = _Map.Layers.Count -1; i >= 0; i--)
            {
                if (!(_Map.Layers is GraphicsLayer) && _Map.Layers.Visible == true)
                {
                        IDTask = new IdentifyTask();
                        IDTask.ExecuteCompleted += IdentifyTask_ExecuteCompleted;
                        IDTask.Failed += IdentifyTask_Failed;
                        IDTask.Url = MapUtils.GetLayerURL(_Map.Layers);                    
                        IDTask.ExecuteAsync(idParams, i); 
                }
             }



In your code it looks like you're just running the Identify against one layer in the QueryPoint_Click routine -
IdentifyTask identifyTask = new IdentifyTask("http://mark-laptopgis/ArcGIS/rest/services/Traffic/TMC/MapServer");
            identifyTask.ExecuteCompleted += IdentifyTask_ExecuteCompleted;
            identifyTask.Failed += IdentifyTask_Failed;
            identifyTask.ExecuteAsync(identifyParams);



If you add the layer id for the Traffic/TMC to the ExcecuteAsync call, the token parameter in AddResultstoControl will not be null.  Or if you will always be using the same service & layer to query for attachments, you could change the logic in AddResultstoControl -


                    urlLayer =   "http://mark-laptopgis/ArcGIS/rest/services/Traffic/TMC/MapServer/" + result.LayerId;

                    flyr = new FeatureLayer();
                    flyr.Url = urlLayer;

0 Kudos
MarkMa
by
New Contributor
Terry, Thank you again for your help. Actually, I do have 6 layers with attachments that I need to display but I use only one layer to do testing. after add more layers in xaml and modify my C# code with you posted code, urlLayer and token value displayed. however, When I run the app and click a point layer to Identify.
I got "Identify faild. Error:ESRI.ArcGIS.Client.Tasks.ServiceException:Unable to complete operation".
When I click OK on the error message, the error point to the line
"_dataLinks[linkId].Add(new DataLink() { Link = ai.Uri, Caption = ai.Name });" with error said.
"The given key was not present in the dictionary". I debug the line and found "ai.Uri" path is correct and "ai.Name" point to a pdf name is correct. In the code I have "Dictionary<string, List<DataLink>> _dataLinks { get; set; }" Do I need to add anything into Dictionary or class? Please teaching me.
Thanks
Mark
0 Kudos
TerryGiles
Occasional Contributor III
Mark,

Sorry for the delay, I've been out of the office.  I recall getting this message while developing the code, but cannot recall what was causing it or if I fixed it.  The error is because the value of linkID is not in _dataLinks as the key.  When I get back to this project, I'll see if I can remember what was causing it and how to correct it.
0 Kudos
MarkMa
by
New Contributor
Terry, Thank you very much for your reply. After study your codes and the concept. View attachments  use Identify tool is working now. You were the first person to post code on the forum to discuss this function. Your post help me to make the function working on my app . I would like to express my appreciate to you for your kindness to teach me and answer my questions on the forum.
Mark Ma
0 Kudos