Use file data with Attachment and Note records

Attachment (ActivityMimeAttachment) and Note (Annotation) tables contain special string columns that store file data.

These tables existed before file or image columns, so they work differently.

Because these columns are part of the data for the attachment or note record, you should update these three columns together with any other values.

Using file data

You can directly get and set the values of the activitymimeattachment.body and annotation.documentbody columns as Base64 encoded strings. This should be fine as long as the files are not too large, for example under 4 MB.

By default the maximum size is 5 MB. You can configure these columns to accept files as large as 128 MB. When you have increased the maximum file size and are working with larger files, you should use messages provided to break the files into smaller chunks when uploading or downloading files. For information about retrieving or changing the file size limits, see File size limits.

Attachment files

An attachment is a file that is associated with an email activity, either directly or though an Email Template (Template). Multiple attachments can be associated with the activity or template.

Note

You can re-use attachment files by setting the activitymimeattachment.attachmentid value to refer to another existing attachment rather than by setting the body, filename, and mimetype properties.

Attachment (ActivityMimeAttachment) should not be confused with activityfileattachment, which supports files associated with the Post table.

Within the Dataverse schema there is also a public table with the name Attachment which is exposed in the Web API as attachment EntityType. This table can be queried and it reflects data in the ActivityMimeAttachment table. But it doesn't support create, retrieve, update, or delete operations. This table doesn't appear within the PowerApps designer.

Upload Attachment files

Use the InitializeAttachmentBlocksUpload, UploadBlock, and CommitAttachmentBlocksUpload messages to upload large files for attachments.

Important

These messages can only be used to create a new attachment. It you try to update an existing attachment with these messages you will get an error that the record already exists.

The following static UploadAttachment method shows how to create a new attachment with a file using the InitializeAttachmentBlocksUploadRequest, UploadBlockRequest, and CommitAttachmentBlocksUploadRequest classes to return a CommitAttachmentBlocksUploadResponse with ActivityMimeAttachmentId and FileSizeInBytes properties.

static CommitAttachmentBlocksUploadResponse UploadAttachment(
   IOrganizationService service,
   Entity attachment,
   FileInfo fileInfo,
   string fileMimeType = null)
{
   if (attachment.LogicalName != "activitymimeattachment")
   {
         throw new ArgumentException(
            "The attachment parameter must be an activitymimeattachment entity.",
            nameof(attachment));
   }

   // body value in activitymimeattachment not needed. Remove if found.
   if (attachment.Contains("body"))
   {
         attachment.Attributes.Remove("body");
   }

   // Try to get the mimetype if not provided.
   if (string.IsNullOrEmpty(fileMimeType))
   {
         var provider = new FileExtensionContentTypeProvider();

         if (!provider.TryGetContentType(fileInfo.Name, out fileMimeType))
         {
            fileMimeType = "application/octet-stream";
         }
   }
   // Don't overwrite mimetype value if it exists
   if (!attachment.Contains("mimetype"))
   {
         attachment["mimetype"] = fileMimeType;
   }

   // Initialize the upload
   InitializeAttachmentBlocksUploadRequest initializeRequest = new()
   {
         Target = attachment
   };

   var initializeResponse =
         (InitializeAttachmentBlocksUploadResponse)service.Execute(initializeRequest);

   string fileContinuationToken = initializeResponse.FileContinuationToken;

   // Capture blockids while uploading
   List<string> blockIds = new();

   using Stream uploadFileStream = fileInfo.OpenRead();

   int blockSize = 4 * 1024 * 1024; // 4 MB

   byte[] buffer = new byte[blockSize];
   int bytesRead = 0;

   long fileSize = fileInfo.Length;

   // The number of iterations that will be required:
   // int blocksCount = (int)Math.Ceiling(fileSize / (float)blockSize);
   int blockNumber = 0;

   // While there is unread data from the file
   while ((bytesRead = uploadFileStream.Read(buffer, 0, buffer.Length)) > 0)
   {
         // The file or final block may be smaller than 4MB
         if (bytesRead < buffer.Length)
         {
            Array.Resize(ref buffer, bytesRead);
         }

         blockNumber++;

         string blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));

         blockIds.Add(blockId);

         // Prepare the request
         UploadBlockRequest uploadBlockRequest = new()
         {
            BlockData = buffer,
            BlockId = blockId,
            FileContinuationToken = fileContinuationToken,
         };

         // Send the request
         service.Execute(uploadBlockRequest);
   }

   // Commit the upload
   CommitAttachmentBlocksUploadRequest commitRequest = new()
   {
         BlockList = blockIds.ToArray(),
         FileContinuationToken = fileContinuationToken,
         Target = attachment
   };
   
      return  (CommitAttachmentBlocksUploadResponse)service.Execute(commitRequest);

}

More information:

Note

This example method includes some logic to try to get the MIME type of the file using the FileExtensionContentTypeProvider.TryGetContentType(String, String) Method if it isn't provided. If not found it will set the mime type to application/octet-stream.

Download Attachment files

You can download an attachment file in a single operation using the Web API, or you can download the attachment file in chunks using the SDK or Web API.

Download Attachment files in a single operation using Web API

Using the Web API, you can download an attachment file in a single operation:

Request

GET [Organization Uri]/api/data/v9.2/activitymimeattachments(<activitymimeattachmentid>)/body/$value HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
If-None-Match: null
Accept: application/json

Response

HTTP/1.1 200 OK
OData-Version: 4.0
Content-Type: text/plain

<Base64 string content removed for brevity>

Note

Unlike retrieving file columns, this method does not provide information about file size, file name, or mime type. More information: Download a file in a single request using Web API and Retrieve the raw value of a property

Download Attachment files in chunks

To retrieve the file in in chunks, you must use the following, with either the SDK or Web API:

Message Description
InitializeAttachmentBlocksDownload Use this message to indicate the note record that you want to download a file from. It returns the file size in bytes and a file continuation token that you can use to download the file in blocks using the DownloadBlock message.
DownloadBlock Request the size of the block, the offset value and the file continuation token.

Once you've downloaded all the blocks, you must join them to create the entire downloaded file.

The following static DownloadAttachment method shows how to download an attachment using the SDK with the InitializeAttachmentBlocksDownloadRequest and DownloadBlockRequest classes. This function returns the byte[] data and the name of the file.

static (byte[] bytes, string fileName) DownloadAttachment(
   IOrganizationService service,
   EntityReference target)
{
   if (target.LogicalName != "activitymimeattachment")
   {
         throw new ArgumentException(
            "The target parameter must refer to an activitymimeattachment record.",
            nameof(target));
   }

   InitializeAttachmentBlocksDownloadRequest initializeRequest = new()
   {
         Target = target
   };

   var response =
         (InitializeAttachmentBlocksDownloadResponse)service.Execute(initializeRequest);

   string fileContinuationToken = response.FileContinuationToken;
   int fileSizeInBytes = response.FileSizeInBytes;
   string fileName = response.FileName;

   List<byte> fileBytes = new(fileSizeInBytes);

   long offset = 0;
   long blockSizeDownload = 4 * 1024 * 1024; // 4 MB

   // File size may be smaller than defined block size
   if (fileSizeInBytes < blockSizeDownload)
   {
         blockSizeDownload = fileSizeInBytes;
   }

   while (fileSizeInBytes > 0)
   {
         // Prepare the request
         DownloadBlockRequest downLoadBlockRequest = new()
         {
            BlockLength = blockSizeDownload,
            FileContinuationToken = fileContinuationToken,
            Offset = offset
         };

         // Send the request
         var downloadBlockResponse =
                  (DownloadBlockResponse)service.Execute(downLoadBlockRequest);

         // Add the block returned to the list
         fileBytes.AddRange(downloadBlockResponse.Data);

         // Subtract the amount downloaded,
         // which may make fileSizeInBytes < 0 and indicate
         // no further blocks to download
         fileSizeInBytes -= (int)blockSizeDownload;
         // Increment the offset to start at the beginning of the next block.
         offset += blockSizeDownload;
   }

   return (fileBytes.ToArray(), fileName);
}

More information:

Annotation files

A note is a record associated with a table row that contains text and may have a single file attached. Only those tables defined with EntityMetadata.HasNotes set to true may have notes associated with them.

Upload Annotation files

Use the InitializeAnnotationBlocksUpload, UploadBlock, and CommitAnnotationBlocksUpload messages to upload files for notes.

Note

The annotation you pass as the Target parameter for these messages must have an annotationid value. This is how you can update existing annotation records.

To create a new annotation with these messages you must generate a new Guid value to set as the annotationid value rather than let Dataverse generate the value. Normally, it is best to let Dataverse generate the unique identifier values when creating new records, but that is not possible with these messages.

The following static UploadNote method shows how to create or update a note with a file using the InitializeAnnotationBlocksUploadRequest, UploadBlockRequest, and CommitAnnotationBlocksUploadRequest classes and it will return a CommitAnnotationBlocksUploadResponse with AnnotationId and FileSizeInBytes properties.

static CommitAnnotationBlocksUploadResponse UploadNote(
   IOrganizationService service,
   Entity annotation,
   FileInfo fileInfo,
   string? fileMimeType = null)
{

   if (annotation.LogicalName != "annotation")
   {
         throw new ArgumentException(
            message: "The annotation parameter must be an annotation entity",
            paramName: nameof(annotation));
   }
   if (!annotation.Attributes.Contains("annotationid") || annotation.Id != Guid.Empty)
   {
         throw new ArgumentException(
            message: "The annotation parameter must include a valid annotationid value.",
            paramName: nameof(annotation));
   }

   // documentbody value in annotation not needed. Remove if found.
   if (annotation.Contains("documentbody"))
   {
         annotation.Attributes.Remove("documentbody");
   }

   // Try to get the mimetype if not provided.
   if (string.IsNullOrEmpty(fileMimeType))
   {
         var provider = new FileExtensionContentTypeProvider();

         if (!provider.TryGetContentType(fileInfo.Name, out fileMimeType))
         {
            fileMimeType = "application/octet-stream";
         }
   }
   // Don't override what might be included in the annotation.
   if (!annotation.Contains("mimetype")) {
         annotation["mimetype"] = fileMimeType;
   }
   
   // Initialize the upload
   InitializeAnnotationBlocksUploadRequest initializeRequest = new()
   {
         Target = annotation
   };

   var initializeResponse =
         (InitializeAnnotationBlocksUploadResponse)service.Execute(initializeRequest);

   string fileContinuationToken = initializeResponse.FileContinuationToken;

   // Capture blockids while uploading
   List<string> blockIds = new();

   using Stream uploadFileStream = fileInfo.OpenRead();

   int blockSize = 4 * 1024 * 1024; // 4 MB

   byte[] buffer = new byte[blockSize];
   int bytesRead = 0;

   long fileSize = fileInfo.Length;

   // The number of iterations that will be required:
   // int blocksCount = (int)Math.Ceiling(fileSize / (float)blockSize);
   int blockNumber = 0;

   // While there is unread data from the file
   while ((bytesRead = uploadFileStream.Read(buffer, 0, buffer.Length)) > 0)
   {
         // The file or final block may be smaller than 4MB
         if (bytesRead < buffer.Length)
         {
            Array.Resize(ref buffer, bytesRead);
         }

         blockNumber++;
         // Generates base64 string blockId values based on a Guid value so they are always the same length.
         string blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));

         blockIds.Add(blockId);

         // Prepare the request
         UploadBlockRequest uploadBlockRequest = new()
         {
            BlockData = buffer,
            BlockId = blockId,
            FileContinuationToken = fileContinuationToken,
         };

         // Send the request
         service.Execute(uploadBlockRequest);
   }

   // Commit the upload
   CommitAnnotationBlocksUploadRequest commitRequest = new()
   {
         BlockList = blockIds.ToArray(),
         FileContinuationToken = fileContinuationToken,
         Target = annotation
   };

      return  (CommitAnnotationBlocksUploadResponse)service.Execute(commitRequest);
}

More information:

Note

This example method includes some logic to try to get the MIME type of the file using the FileExtensionContentTypeProvider.TryGetContentType(String, String) Method if it isn't provided. If not found it will set the mime type to application/octet-stream.

Download Annotation files

You can download a note file in a single operation using the Web API, or you can download the note file in chunks using the SDK or Web API.

Download Annotation files in a single operation using Web API

Using the Web API, you can download an note file in a single operation:

Request

GET [Organization Uri]/api/data/v9.2/annotations(<annotationid>)/documentbody/$value HTTP/1.1
OData-MaxVersion: 4.0
OData-Version: 4.0
If-None-Match: null
Accept: application/json

Response

HTTP/1.1 200 OK
OData-Version: 4.0
Content-Type: text/plain

<Base64 string content removed for brevity>

Note

Unlike retrieving file columns, this method does not provide information about file size, file name, or mime type. More information: Download a file in a single request using Web API and Retrieve the raw value of a property

Download Annotation files in chunks

To retrieve the file in in chunks, you must use the following, with either the SDK or Web API:

Message Description
InitializeAnnotationBlocksDownload Use this message to indicate the note record that you want to download a file from. It returns the file size in bytes and a file continuation token that you can use to download the file in blocks using the DownloadBlock message.
DownloadBlock Request the size of the block, the offset value and the file continuation token.

Once you've downloaded all the blocks, you must join them to create the entire downloaded file.

The following static DownloadNote method shows how to download an note using the SDK with the InitializeAnnotationBlocksDownloadRequest and DownloadBlockRequest classes. This function returns the byte[] data and the name of the file.

static (byte[] bytes, string fileName) DownloadNote(
    IOrganizationService service,
    EntityReference target)
{
if (target.LogicalName != "annotation")
{
      throw new ArgumentException("The target parameter must refer to an note record.", nameof(target));
}

InitializeAnnotationBlocksDownloadRequest initializeRequest = new()
{
      Target = target
};

var response =
      (InitializeAnnotationBlocksDownloadResponse)service.Execute(initializeRequest);

string fileContinuationToken = response.FileContinuationToken;
int fileSizeInBytes = response.FileSizeInBytes;
string fileName = response.FileName;

List<byte> fileBytes = new(fileSizeInBytes);

long offset = 0;
long blockSizeDownload = 4 * 1024 * 1024; // 4 MB

// File size may be smaller than defined block size
if (fileSizeInBytes < blockSizeDownload)
{
      blockSizeDownload = fileSizeInBytes;
}

while (fileSizeInBytes > 0)
{
      // Prepare the request
      DownloadBlockRequest downLoadBlockRequest = new()
      {
            BlockLength = blockSizeDownload,
            FileContinuationToken = fileContinuationToken,
            Offset = offset
      };

      // Send the request
      var downloadBlockResponse =
                  (DownloadBlockResponse)service.Execute(downLoadBlockRequest);

      // Add the block returned to the list
      fileBytes.AddRange(downloadBlockResponse.Data);

      // Subtract the amount downloaded,
      // which may make fileSizeInBytes < 0 and indicate
      // no further blocks to download
      fileSizeInBytes -= (int)blockSizeDownload;
      // Increment the offset to start at the beginning of the next block.
      offset += blockSizeDownload;
}

return (fileBytes.ToArray(), fileName);
}

More information:

File size limits

The Organization.MaxUploadFileSize column specifies the maximum allowed size of an a file (in bytes) for an attachment and note, as well as other kinds of data, such as web resource files used for model-driven apps.

The default size is 5 MB (5242880 bytes) and the maximum value is 128 MB (131072000 bytes) and can be set in the email settings for the environment. More information: Manage email settings

If you try to upload a file that is too large, you'll get the following error:

Name: unManagedidsattachmentinvalidfilesize
Code: 0x80044a02
Number: -2147202558
Message: Attachment file size is too big.

Note

The maximum upload file size limit applies to the size of the file in Base64 encoding. A Base64 encoding produces a string that is larger than the original byte[] file data..

Retrieve max upload file size

Use the following ways to retrieve the maximum upload file size.

Use a static method like the following GetMaxUploadFileSize to get the value.

public static int GetMaxUploadFileSize(IOrganizationService service) {

   QueryExpression query = new("organization") { 
         ColumnSet = new ColumnSet("maxuploadfilesize")
   };

   EntityCollection organizations = service.RetrieveMultiple(query);

   // There is only one row in organization table
   return (int)organizations.Entities.FirstOrDefault()["maxuploadfilesize"];
}

More information:

Update max upload file size

Use the following to set the organization.maxuploadfilesize value.

Use a static method like the following SetMaxUploadFileSize to set the maximum upload file size.

public static void SetMaxUploadFileSize(
    IOrganizationService service, 
    int maxUploadFileSizeInBytes)
{
   if (maxUploadFileSizeInBytes > 131072000 || maxUploadFileSizeInBytes < 1) {
         throw new ArgumentOutOfRangeException(nameof(maxUploadFileSizeInBytes), 
         "The maxUploadFileSizeInBytes parameter must be less than 131072000 bytes and greater than 0 bytes.");
   }

   QueryExpression query = new("organization")
   {
         ColumnSet = new ColumnSet("organizationid")
   };

   EntityCollection organizations = service.RetrieveMultiple(query);

   // There is only one row in organization table
   Entity organization = organizations.Entities.FirstOrDefault();
   organization["maxuploadfilesize"] = maxUploadFileSizeInBytes;

   service.Update(organization);
}

More information:

See also

Files and images overview
Sample: File operations with Attachments and Notes using the Dataverse SDK for .NET
Sample: Attachment and Annotation file operations using Dataverse Web API