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.
- The binary file data is stored as Base64 encoded string values in string columns: ActivityMimeAttachment.Body and Annotation.DocumentBody.
- File name data is stored in the
FileName
column. - Mime type data is stored in the
MimeType
column.
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
Feedback
Submit and view feedback for