Getting Attachments for a MapServer (not FeatureServer)

3600
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
JenniferNery
Esri Regular Contributor
FeatureLayer can come from a map service (MapServer). You should still be able to call QueryAttachmentInfos() on this type of FeatureLayer but it would be read-only so you cannot add/delete attachments.
0 Kudos
TerryGiles
Occasional Contributor III
Thanks Jennifer, I'll give it a try.  Not sure if it'll help though as the API docs state the FeatureLayerInfo will not return ObejctIDField or HasAttachments for a Map Server, so I'll still have to loop through the attributes to get the OID and hit the REST endpoint to check HasAttachments.
0 Kudos
TerryGiles
Occasional Contributor III
It looks like the API documentation for FeatureLayerInfo is not correct.  Initializing a FeatureLayer from a Map Server Layer includes correct values for hasAttachments and ObjectIDField.  Can we get the online docs corrected?


Thanks again Jennifer,

Terry
0 Kudos
JenniferNery
Esri Regular Contributor
Thank you for reporting this. We'll try to get our documentation fixed.
0 Kudos
TerryGiles
Occasional Contributor III
Thanks again & if anyone is interested, here's what I ended up with -

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

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


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

                Graphic feature = null;
                string title = string.Empty;
                string urlLayer = string.Empty;
                string urlFeature = string.Empty;
                

                FeatureLayer flyr = null;

                foreach (IdentifyResult result in results)
                {
                    urlFeature = string.Empty;
                
                    feature = result.Feature;
                    title = result.Value.ToString() + " (" + result.LayerName + ")";

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

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

                    //recreating the feature and title otherwise only get the last feature 
                    //see http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
                    Graphic f2 = feature;
                    string t2 = title;

                    flyr.InitializationFailed += FLayer_InitializationFailed;
                    flyr.Initialized += (s, e) =>
                    {
                        
                        string title2 = t2;
                        Graphic feat =  f2;
                        FeatureLayer fl = s as FeatureLayer;
                        FeatureLayerInfo fli = fl.LayerInfo;

                        urlFeature = urlLayer + "/" + feat.Attributes[fli.ObjectIdField];

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

                        cbxIdentify.ItemsSource = _dataItems;

                        if (fli.HasAttachments)
                        {
                            fl.QueryAttachmentInfos(feat, FLayer_QryAttachmentsComplete, FLayer_QryAttachmentsFail);
                        }

                    };
                    flyr.Initialize();
                }

            }
        }


        void FLayer_InitializationFailed(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine((sender as FeatureLayer).Url + " failed");
        }


        void FLayer_QryAttachmentsComplete(IEnumerable<AttachmentInfo> AInfos)
        {

            if (AInfos.Count() < 1)
            {
                return;
            }
            
            string linkId = AInfos.First().Uri.ToString().Replace("/attachments/" + AInfos.First().ID, "");

            foreach (AttachmentInfo ai in AInfos)
            {
                _dataLinks[linkId].Add(new DataLink() { Link = ai.Uri, Caption = ai.Name });
            }
        }

        void FLayer_QryAttachmentsFail(Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
        }


I also changed the _dataItems for a List<DataItem> to ObservableCollection<DataItem> & implemented InfotifyPropertyChange in the DataItem class.  I had to do this to get the combobox items to refresh between identifies
0 Kudos
MarkMa
by
New Contributor
Terry, I am very interested your code and tried to use the code that you have posted. But I can not get it to work. Did I missing anything? A class or method? Would you please teaching me what I need to do to make it work.
I would like to use Silverlight Identify Tool to display attachments link and open attachments (pdf or txt) use MapServer REST Endpoint.
I have published REST MapServer with "HasAttachments" is true. I am use ArcGIS Server 10 with Oracle 10g R2 for ArcSDE. API for SL 2.1. & VS 2010 & SL SDK 4.

Thank you in advance for you help.

Mark Ma
GMB Engineers & Planners.
0 Kudos
TerryGiles
Occasional Contributor III
Mark,

What kind of errors are you getting and where?  Post up some code and xaml along with where you're seeing problems and we can try to figure it out.

TG
0 Kudos
MarkMa
by
New Contributor
Terry, Thank you very much for your reply. I attached a zip file which including xaml, C#,error messages and MapServer service information that you may needed to see where my errors. I have used ESRI Resource Center Silverlight Interactive SDK Query/Identify "http://help.arcgis.com/en/webapi/silverlight/samples/start.htm#Identify" as basemap and input my MapServer and apply your sample code. I appreciate your efforts and time for help me. Happy 4th of July.

Mark
0 Kudos
TerryGiles
Occasional Contributor III
Mark,

Should be easy enough to fix.  Looking at the errors you're seeing, here's what you should change -

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.


2.  "The name '_Map' does not exist in the current context".  _Map is a reference to my map object. Looks like yours is called MyMap, just swap the two.

3.  "object' does not contain a definition for 'Add'".  _dataLinks is defined in your code a a generic object.  In mine it 's  a Dictionary<string,List<DataLink>> _dataLinks { get; set; }

4.  "Cannot apply indexing with [] to an expression of type 'object' ".  redefining _dataLinks as a Dictionary will fix this too.

Hope this helps, 
Terry
0 Kudos