# How to Retrieve High-Resolution Images in Dynamics 365 Plugins & Code Activities

When developing **Plugins** or **Custom Workflow Activities** in Dynamics 365 CE or PowerApps (Dataverse), a common roadblock is retrieving image fields. If you access an image attribute using standard `Retrieve` or `RetrieveMultiple(FetchXML)` logic, Dataverse returns a low-resolution **thumbnail** by default.

To get the **original, full-resolution image**, you must use the file streaming message requests. This post provides a clean, reusable approach to extracting full image data as either a `Base64` string or raw `byte[]` data.

* * *

### The Problem: The "Thumbnail Trap"

In Dataverse, image fields are optimized for performance. When you call `entity.GetAttributeValue<byte[]>("your_field")`, the platform serves a compressed version to save bandwidth. For high-quality email signatures, document generation, or external integrations, this thumbnail is usually too blurry to use.

### The Solution: Multi-Block Downloading

To bypass compression, you must treat the image as a file and download it in "blocks" (chunks). This ensures you pull the raw, uncompressed data directly from the storage layer.

* * *

## The Implementation

Copy the following methods into your helper class. This logic is sandbox-safe and designed for high performance within the 2-minute plugin execution limit.

*(Note: Ensure you have* `Microsoft.Crm.Sdk.Messages` *included in your using statements).*

### 1\. The Entry Function: `GetFullImageContent`

This is the method you will call from your main logic. It allows you to toggle the return format using a Boolean parameter.

```csharp
/// <summary>
/// Retrieves full-resolution image data from a record.
/// </summary>
/// <param name="service">The IOrganizationService instance.</param>
/// <param name="entityName">Logical name of the entity (e.g., "account").</param>
/// <param name="recordId">The GUID of the specific record.</param>
/// <param name="imageFieldName">The logical name of the image attribute.</param>
/// <param name="asBase64">Set to true for Base64 string, false for byte array string.</param>
/// <returns>A string containing the image data.</returns>
public string GetFullImageContent(IOrganizationService service, string entityName, Guid recordId, string imageFieldName, bool asBase64)
{
    if (recordId == Guid.Empty || string.IsNullOrEmpty(entityName) || string.IsNullOrEmpty(imageFieldName))
        return string.Empty;

    // Create the reference to the target record
    EntityReference recordRef = new EntityReference(entityName, recordId);

    // Call the helper to stream the full bytes
    byte[] imageBytes = DownloadFullImage(service, recordRef, imageFieldName);

    if (imageBytes == null || imageBytes.Length == 0)
        return string.Empty;

    // Return the format requested by the caller
    return asBase64 ? Convert.ToBase64String(imageBytes) : BitConverter.ToString(imageBytes);
}
```

### 2\. The Internal Streaming Helper: `DownloadFullImage`

This method manages the initialization and the loop required to piece the image chunks back together.

```csharp
private byte[] DownloadFullImage(IOrganizationService service, EntityReference recordRef, string attributeName)
{
    // 1. Initialize the download session
    var initializeRequest = new InitializeFileBlocksDownloadRequest()
    {
        Target = recordRef,
        FileAttributeName = attributeName
    };

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

    string fileToken = initializeResponse.FileContinuationToken;
    long fileSize = initializeResponse.FileSizeInBytes;
    List<byte> fullFile = new List<byte>((int)fileSize);

    long offset = 0;
    long blockSize = 4 * 1024 * 1024; // 4 MB Chunk Size

    // 2. Loop through and download blocks until complete
    while (offset < fileSize)
    {
        var downloadBlockRequest = new DownloadBlockRequest()
        {
            BlockLength = blockSize,
            FileContinuationToken = fileToken,
            Offset = offset
        };

        var downloadBlockResponse = (DownloadBlockResponse)service.Execute(downloadBlockRequest);
        
        fullFile.AddRange(downloadBlockResponse.Data);
        
        // Move the offset forward by the actual amount of data received
        offset += downloadBlockResponse.Data.Length;
    }

    return fullFile.ToArray();
}
```

### Key Technical Considerations

*   **Enable "Store Full Image":** This code will only return the high-resolution file if the "Store full image" option is enabled in the column's definition within the Power Apps Maker Portal.
    
*   **SDK Versions:** Ensure your project is referencing `Microsoft.CrmSdk.CoreAssemblies` (v9.0.2.x or higher) to access the `InitializeFileBlocksDownloadRequest` and `DownloadBlockRequest` classes.
    
*   **Performance:** While 4MB is the standard block size, the code will automatically handle smaller files in a single pass.
    
*   **Base64 Use Case:** Use `asBase64 = true` if you are injecting the image into an HTML template or returning it via a Web API.
    
*   **Error Handling:** In a production environment, always wrap these calls in a `try-catch` block and use the `ITracingService` to monitor the `fileSize` and `offset` during execution.
    

* * *

### Summary

By ensuring your Dataverse columns are set to **Store full image** and using the **Initialize/Download pattern** instead of direct attribute access, you can ensure your Dynamics 365 solutions handle images with the highest possible fidelity.

Happy coding, Wizards!
