Work with feature attachments

A feature service can be configured to allow a unique collection of files to be associated with each feature. These feature attachments can consist of images, audio, video, text, and other files that provide additional information. A feature may have zero or several associated attachments that are managed by the feature service. Using ArcGIS Runtime SDK for .NET, you can query a feature for its attachments to view them as well as add, update, and delete attachments.

Feature attachments listed in a pop-up


The following file extensions are supported for feature attachments: 7Z, AIF, AVI, BMP, DOC, DOCX, DOT, ECW, EMF, EPA, GIF, GML, GTAR, GZ, IMG, J2K, JP2, JPC, JPE, JPEG, JPF, JPG, JSON, MDB, MID, MOV, MP2, MP3, MP4, MPA, MPE, MPEG, MPG, MPV2, PDF, PNG, PPT, PPTX, PS, PSD, QT, RA, RAM, RAW, RMI, SID, TAR, TGZ, TIF, TIFF, TXT, VRML, WAV, WMA, WMF, WPS, XLS, XLSX, XLT, XML, and ZIP.

When creating a local geodatabase from a feature service, you can control whether or not attachments are included in the output data. See the Create an offline map topic for more information and an example.

Query attachments

Attachments must be enabled for a service before you can store them for features. Before attempting to get attachments for a feature, first check to see if the layer allows them. Even if attachments have been enabled for a feature service, it's possible that many features in the dataset will not have associated attachments. Do not assume that all features in an attachment-enabled service will have attachments defined.

Enable attachments for a feature layer hosted with ArcGIS Online

To check whether a feature layer supports attachments, use the HasAttachments property defined on the associated ArcGISFeatureTable. The following code checks whether attachments are supported for any of the feature layers in the map:

// get all feature layers from the map
var featureLayers = MyMapView.Map.Layers.OfType<FeatureLayer>();
// loop thru all feature layers, see if they support attachments
foreach (var lyr in featureLayers)
    var table = lyr.FeatureTable as ArcGISFeatureTable;
    if(table == null) { continue; }

    var hasAttachments = table.HasAttachments;
    // ... do something with attachments ...

After verifying that a feature table supports them, information about attachments can be returned for a specified feature using the QueryAttachmentsAsync method on ArcGISFeatureTable. The ID of the feature is passed into the method and an AttachmentInfos object is returned.


For an online feature layer, ServiceFeatureTable provides an overload that allows you to run the query on the local cache of the table rather than on the service. This is more efficient if you're confident that the cached data is still accurate, since it saves a round trip to the server.

The AttachmentInfos result provides a collection of AttachmentInfo objects with metadata about each attachment. If the feature doesn't have attachments, the collection will be empty. The following example queries the table for attachments for a specified feature. A check is then made to determine if the Infos collection is empty.

// query attachments for a feature
var attachmentResult = await featureTable.QueryAttachmentsAsync(featureId);

// check if any were found
if (attachmentResult.Infos != null && attachmentResult.Infos.Any())
    foreach (var info in attachmentResult.Infos)
        // ... process each attachment result ...

Get attachment information

When a query for attachments is made, as described previously, a collection of AttachmentInfo objects is returned. The AttachmentInfo class contains properties that describe basic information about an attachment, including the file name, ID, content type, and size (in bytes). For improved performance, the attachment data (image or document, for example) is not returned unless it is specifically requested.

The following example reports basic information for a feature's attachments, including the ID, file name, content type, and file size for each attachment:

foreach (var info in attachmentResult.Infos)
    var item = string.Format("{0}: {1} - {2} ({3} bytes)", info.ID.ToString(), info.ContentType, info.Name, info.Size);
Feature attachment metadata

Get attachment data

To get attachment data, call the GetDataAsync method on AttachmentInfo. The following example iterates over all attachments for a feature and creates a BitmapImage to display all image attachments:

// loop thru all AttachmentInfo objects for the feature
foreach (var info in attachmentResult.Infos)
    // find image attachments: "image/jpeg", "image/png", etc.
    if (info.ContentType.Contains(@"image/"))
        // get the data (stream) from the AttachmentInfo
        using (var stream = await info.GetDataAsync())
            // create a new BitmapImage and set the source with the stream
            var bitmap = new BitmapImage();
            bitmap.StreamSource = stream;

            // create a new Image control to display the bitmap
            var img = new Image();
            img.Source = bitmap;
            // show the file name in the tool tip
            img.ToolTip = info.Name;

            // add the Image to a StackPanel on the page
Display image attachments for a feature

Edit feature attachments

If permitted by the service, you can add, update, and delete feature attachments using ArcGIS for Desktop, the ArcGIS Online map viewer, or your ArcGIS Runtime SDK for .NET app. The service may restrict such edits based on property settings, available capabilities, or ownership-based access rules. Before attempting to make edits to feature attachments, check to see which types of edits are supported.

As described previously, you can use ArcGISFeatureTable.HasAttachments to see if the table supports feature attachments. Similarly, availability of particular edits to feature attachments can be checked using the CanAddAttachment, CanDeleteAttachment, and CanUpdateAttachment methods. Each of these methods takes the feature associated with the attachment as an input and returns a Boolean result that indicates whether or not the operation is supported. Obviously, a supported edit operation can still fail. To evaluate the success of an edit to a feature attachment, you can check the value of the Error property on the returned AttachmentResult objects.

As with edits to a feature's attributes or geometry, edits made to feature attachments are initially stored in an in-memory version of the feature table. To push attachment edits to the feature service that hosts the data, you must apply your in-memory edits by calling ApplyAttachmentEditsAsync on the ServiceFeatureTable. When or how often you call ApplyAttachmentEditsAsync to push your edits to the service is up to you and depends on the details of your editing workflow. Some factors that might influence your code are the frequency of edits being made, the importance of keeping data in the service current, and the possibility of losing in-memory edits.

To revert to your original data (that is, discard edits made since the last sync operation), call the ClearEdits method on the table. This will discard all edits made, including feature and feature attachment edits.

The ArcGIS Runtime SDK for .NET toolkit contains an AttachmentEditor control for use in your ArcGIS Runtime apps. See Install the toolkit for instructions for downloading and installing the toolkit. The Edit attachment sample shows how to add, update, and delete feature attachments. You can clone or download the source code for all the samples from the arcgis-runtime-samples-dotnet GitHub repository.

Add a new attachment

Use the AddAttachmentAsync method on ArcGISFeatureTable to add a new attachment for a feature. The method requires the ID of the target feature, a Stream representing the data to attach, and a file name. As discussed previously, edits made to the feature table are stored in-memory. To push attachment edits to the service, you must call ApplyAttachmentEditsAsync on the ServiceFeatureTable.

A new attachment can be added for a feature if:

  • The service supports adding attachments (CanAddAttachment returns true).
  • The file size is under the maximum value set for upload to the service. By default, this value is 2 GB but can be changed by the server administrator.
  • The file is one of the supported types (listed previously in this topic).

The following example adds a text file as a feature attachment. After adding the attachment, the edits are applied to the underlying feature service. A check is made after both making and applying the edit, and an exception is thrown if an error is found.

    // get data from a local file to add as an attachment 
    var fileStream = System.IO.File.OpenRead(@"C:\Data\Products_4355.txt");

    // add the data as an attachment to a feature in the (in-memory) feature table
    var addResult = await featureTable.AddAttachmentAsync(featureId, fileStream, "ProductList.txt");
    if (addResult.Error != null)
        // problem adding the attachment to the in-memory feature table
        throw new Exception("Error adding attachment: " + addResult.Error.Message);

    // apply the attachment edit back to the feature service
    var applyResult = await featureTable.ApplyAttachmentEditsAsync();

    // check the edit results (specifically, the single Add result) for success
    var serverAddResult = applyResult.AddResults.FirstOrDefault();
    if (serverAddResult.Error != null)
        // problem applying the attachment edit to the service
        throw new Exception("Error applying attachment edit: " + serverAddResult.Error.Message);
catch (Exception exp)
    // report the exception

Attachment edits may be successful in the in-memory feature table but fail when applied to the feature service. An unsupported file extension, for example, does not fail when you add an attachment to the in-memory table but does when edits are applied to the service. Since no exceptions are thrown, check the attachment edit results, as shown in the previous example.

Delete attachments

One or more attachments can be deleted for a feature by calling DeleteAttachmentsAsync on ArcGISFeatureTable. Pass in the ID of the feature and an array specifying the IDs of the attachments to delete. Before attempting to delete attachments, check the value of CanDeleteAttachment to verify that the operation is supported. Finally, to apply all current attachment edits from the in-memory feature table to the underlying feature service, call ApplyAttachmentEditsAsync on ServiceFeatureTable.

The following example illustrates using CanDeleteAttachment to determine if deleting attachments is allowed by the service. After deleting attachments, it checks the AttachmentResult collection for errors to determine if the edits were successful. The in-memory edits are then pushed to the service by calling ApplyAttachmentEditsAsync before checking for errors a final time.

// see if deleting attachments is possible
var deleteOK = featureTable.CanDeleteAttachment(fromFeature);

// if delete is possible, try to delete three attachments from a particular feature
if (deleteOK)
    // identify the IDs of the attachments to be removed
    long[] attachmentIds = new long[3] { 43, 16, 21 };

    // delete the specified attachments for feature with ID = featureId
    // (attachments are deleted from the in-memory version of the feature table)
    var inMemoryDeleteResult = await featureTable.DeleteAttachmentsAsync(featureId, attachmentIds);

    // check for errors in the in-memory attachment deletions
    var inMemoryEditErrors = from m in inMemoryDeleteResult.Results where m.Error != null select m;
    if (inMemoryEditErrors.Any())
        // ... report errors here ...

    // apply the attachment edits to the feature service, get results
    var applyResults = await featureTable.ApplyAttachmentEditsAsync();

    // get the delete attachment results for the one feature that was edited
    var deletedAttachments = applyResults.DeleteResults.FirstOrDefault();

    // check for errors in applying the (three) attachment deletions to the service
    var serviceEditErrors = from r in deletedAttachments.Results where r.Error != null select r;

    if (serviceEditErrors.Any())
        // ... report errors here ...

Update an attachment

Updating an attachment is essentially the same as deleting an existing attachment and re-adding it with new data or properties. A common scenario for updating a feature attachment is to replace the information stored in the attachment with more up-to-date data. A fast food restaurant feature, for example, may have a text file attachment that shows current specials. The data held by the attachment could be updated as the information changes without changing the name, ID, or content type of the attachments.

To update an attachment, call UpdateAttachmentAsync on ArcGISFeatureTable and pass in the ID of the target feature, the ID of the attachment to update, the data stream for the attached file, the file name to use for the attachment, and (optionally) the content type.

A feature attachment can be updated if:

  • The service supports updating attachments (CanUpdateAttachment returns true).
  • The size of the new file is under the maximum value set for upload to the service. By default, this value is 2 GB but can be changed by the server administrator.
  • The updated file is one of the supported types (listed previously in this topic).

The following example queries a feature for its attachments and loops through them to find one that needs to be updated (identifying it by name). Once found, the ID of the attachment is used in a call to UpdateAttachmentAsync to replace the attachment data with the contents of a new file. The name of the attachment is left as is.

// get attachments for a specific feature
var attachmentResult = await featureTable.QueryAttachmentsAsync(featureId);

// loop thru all attachments and find the one that needs updating (by name)
foreach (var info in attachmentResult.Infos)
    if (info.Name == "WeeklySpecials.txt")
        // read the updated information into a stream
        var fileStream = System.IO.File.OpenRead(@"C:\Data\Specials_022815.txt");
        // update this attachment with the new info (can keep the same name for the attachment)
        var updateResult = await featureTable.UpdateAttachmentAsync(featureId, info.ID, fileStream, "WeeklySpecials.txt");

        // check the update result for an error
        if (updateResult.Error != null)
            // problem updating the attachment in the in-memory feature table
            throw new Exception("Error updating attachment: " + updateResult.Error.Message);

        // apply the attachment edit back to the feature service
        var applyResult = await featureTable.ApplyAttachmentEditsAsync();

        // check the edit results (specifically, the first Update result) for an error
        var serverUpdateResult = applyResult.UpdateResults.FirstOrDefault();
        if (serverUpdateResult.Error != null)
            // problem updating the attachment
            throw new Exception("Error applying edits to the service: " + serverUpdateResult.Error.Message);

Related topics