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 from those tables.
- The binary file data is stored as Base64-encoded string values in string columns. Attachments are stored in the column
ActivityMimeAttachment.Body
and notes are stored in the columnAnnotation.DocumentBody
. - File name data is stored in the
FileName
column. - MIME type data is stored in the
MimeType
column.
Because FileName
, MimeType
, ActivityMimeAttachment.Body
, and Annotation.DocumentBody
are part of the data for the attachment or note record, you should update these three columns together with any other values.
You can directly get and set the values of the activitymimeattachment.body
and annotation.documentbody
columns as Base64-encoded strings. Setting these values should be fine as long as the files aren't 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 through an Email Template (Template). Multiple attachments can be associated with the activity or template. You can reuse attachment files by setting the activitymimeattachment.attachmentid
value to refer to another existing attachment rather than by setting the body
, filename
, and mimetype
properties.
Other Dataverse tables named attachment
Attachment (ActivityMimeAttachment) shouldn't be confused with activityfileattachment, which supports files associated with the Post table.
Within the Dataverse schema, there's 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 Power Apps designer.
Upload attachment files
Use the InitializeAttachmentBlocksUpload
, UploadBlock
, and CommitAttachmentBlocksUpload
messages to upload large files for attachments.
Important
You can only use these messages to create a new attachment. It you try to use them to update an existing attachment you'll get an error that the record already exists.
The following static UploadAttachment
method shows how to create an 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 the type isn't found, it's set to application/octet-stream
.
Download attachment files
You can download an attachment file in a single operation using the Web API or in chunks using the SDK or Web API.
Download attachment files in a single operation using the Web API
Using the Web API, you can download an attachment file in a single operation.
Unlike retrieving file columns, this method doesn't provide information about file size, file name, or MIME type.
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>
More information:
Download attachment files in chunks
To retrieve the file in chunks, use the following messages with either the SDK or Web API:
Message | Description |
---|---|
InitializeAttachmentBlocksDownload |
Specifies 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 |
Requests the size of the block, the offset value, and the file continuation token. |
After you download all the blocks, 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 tables 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.
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.
Normally, it's best to let Dataverse generate the unique identifier values when creating new records, but that isn't possible with these messages. 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.
The following static UploadNote
method shows how to create or update a note with a file using the InitializeAnnotationBlocksUploadRequest, UploadBlockRequest, and CommitAnnotationBlocksUploadRequest classes. It returns 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 the type isn't found, it's set to application/octet-stream
.
Download annotation files
You can download a Note file in a single operation using the Web API, or in chunks using the SDK or Web API.
Download Annotation files in a single operation using the Web API
Using the Web API, you can download a Note file in a single operation:
Unlike retrieving file columns, this method doesn't provide information about file size, file name, or MIME type.
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>
More information:
Download annotation files in chunks
To retrieve the file in chunks, use the following messages with either the SDK or Web API:
Message | Description |
---|---|
InitializeAnnotationBlocksDownload |
Specifies 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 |
Requests the size of the block, the offset value, and the file continuation token. |
After you download all the blocks, join them to create the entire downloaded file.
The following static DownloadNote
method shows how to download a 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 a file in bytes for an attachment and note, and other kinds of data, such as web resource files used for model-driven apps. 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.
The default size is 5 MB (5,242,880 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's too large, you get the following error:
Name:
unManagedidsattachmentinvalidfilesize
Code:0x80044a02
Number:-2147202558
Message:Attachment file size is too big.
Retrieve max upload file size
You can retrieve the maximum upload file size in a couple of ways.
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:
Change max upload file size
You can set the organization.maxuploadfilesize
value in a couple of ways.
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