As Microsoft continues to evolve the Azure SDKs, developers working with Dynamics 365 Finance and Operations (D365FO) need updated and future-proof approaches for managing blob storage operations — especially when integrating with external C# libraries via .NET assemblies.
In this article, I’ll introduce a modern C# helper class built using the new Azure.Storage.Blobs SDK (which replaces the deprecated Microsoft.Azure.Storage.* libraries). This helper is designed to be used from X++ code via a reference to a compiled .NET assembly.
✅ Why Use This Helper?
The AzureBlobStorageHelper class is:
- 100% compliant with the latest
Azure.Storage.BlobsSDK. - Designed with compatibility in mind for X++ runtime usage in D365FO.
- Lightweight, reliable, and free from deprecated APIs.
- Covers all common blob operations: upload, download, delete, copy, and list.
- Provides advanced support for working with blobs as
MemoryStream,byte[], or directly viaBlobClient.
🔧 Setup: C# Helper Class
The core class is defined in a .NET Framework Class Library project (recommended: .NET Framework 4.7.2 for compatibility). Here’s the signature:
public class AzureBlobStorageHelper
{
public AzureBlobStorageHelper(string connectionString);
public bool UploadBlobFromString(string containerName, string blobName, string content);
public string DownloadBlobAsString(string containerName, string blobName);
public bool DeleteBlob(string containerName, string blobName);
public List<BlobClient> ListBlobsAsBlobClient(string containerName, string prefix);
public MemoryStream DownloadBlobAsMemoryStream(string containerName, string blobName);
public bool UploadBlobFromMemoryStream(string containerName, string blobName, MemoryStream stream);
// ... and many more
}
The class internally uses:
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
This ensures compatibility with all current and future Azure Storage enhancements.
📄 Full Source Code – AzureBlobStorageHelper.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure;
namespace AzureBlobStorageHelperNS
{
/// <summary>
/// Helper class per operazioni Azure Storage da utilizzare con X++
/// </summary>
public class AzureBlobStorageHelper
{
private readonly BlobServiceClient _blobServiceClient;
private readonly string _connectionString;
/// <summary>
/// Costruttore con connection string
/// </summary>
/// <param name="connectionString">Connection string di Azure Storage</param>
public AzureBlobStorageHelper(string connectionString)
{
_connectionString = connectionString;
_blobServiceClient = new BlobServiceClient(connectionString);
}
/// <summary>
/// Verifica se un container esiste
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <returns>True se il container esiste</returns>
public bool ContainerExists(string containerName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
return containerClient.Exists();
}
catch (Exception ex)
{
throw new Exception($"Errore nel verificare l'esistenza del container: {ex.Message}");
}
}
/// <summary>
/// Crea un container se non esiste
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <returns>True se il container è stato creato o esisteva già</returns>
public bool CreateContainerIfNotExists(string containerName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var response = containerClient.CreateIfNotExists(PublicAccessType.None);
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nella creazione del container: {ex.Message}");
}
}
/// <summary>
/// Carica un blob da stringa
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <param name="content">Contenuto da caricare</param>
/// <returns>True se il caricamento è riuscito</returns>
public bool UploadBlobFromString(string containerName, string blobName, string content)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
blobClient.Upload(stream, overwrite: true);
}
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nel caricamento del blob: {ex.Message}");
}
}
/// <summary>
/// Carica un blob da file
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <param name="filePath">Percorso del file locale</param>
/// <returns>True se il caricamento è riuscito</returns>
public bool UploadBlobFromFile(string containerName, string blobName, string filePath)
{
try
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"File non trovato: {filePath}");
}
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
blobClient.Upload(filePath, overwrite: true);
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nel caricamento del blob da file: {ex.Message}");
}
}
/// <summary>
/// Scarica un blob come stringa
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>Contenuto del blob come stringa</returns>
public string DownloadBlobAsString(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
if (!blobClient.Exists())
{
throw new Exception($"Blob non trovato: {blobName}");
}
var response = blobClient.DownloadContent();
return response.Value.Content.ToString();
}
catch (Exception ex)
{
throw new Exception($"Errore nel download del blob: {ex.Message}");
}
}
/// <summary>
/// Scarica un blob in un file
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <param name="downloadPath">Percorso dove salvare il file</param>
/// <returns>True se il download è riuscito</returns>
public bool DownloadBlobToFile(string containerName, string blobName, string downloadPath)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
if (!blobClient.Exists())
{
throw new Exception($"Blob non trovato: {blobName}");
}
blobClient.DownloadTo(downloadPath);
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nel download del blob su file: {ex.Message}");
}
}
/// <summary>
/// Elimina un blob
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>True se l'eliminazione è riuscita</returns>
public bool DeleteBlob(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
// Verifica l'esistenza del blob
var exists = blobClient.Exists();
if (!exists.Value)
{
return false; // Il blob non esiste
}
// Elimina il blob
blobClient.Delete();
return true;
}
catch (RequestFailedException ex)
{
// Gestione specifica per Azure
throw new Exception($"Errore Azure durante eliminazione blob: {ex.Message}");
}
catch (Exception ex)
{
// Gestione generica
throw new Exception($"Errore generico durante eliminazione blob: {ex.Message}");
}
}
/// <summary>
/// Verifica se un blob esiste
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>True se il blob esiste</returns>
public bool BlobExists(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
return blobClient.Exists();
}
catch (Exception ex)
{
throw new Exception($"Errore nel verificare l'esistenza del blob: {ex.Message}");
}
}
/// <summary>
/// Lista tutti i blob in un container
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <returns>Lista dei nomi dei blob</returns>
public List<string> ListBlobs(string containerName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobNames = new List<string>();
foreach (var blobItem in containerClient.GetBlobs())
{
blobNames.Add(blobItem.Name);
}
return blobNames;
}
catch (Exception ex)
{
throw new Exception($"Errore nel listare i blob: {ex.Message}");
}
}
/// <summary>
/// Lista i blob in un container con un prefisso specificato e restituisce i blob come MemoryStream
/// </summary>
/// <param name="containerName"></param>
/// <param name="prefix"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public List<MemoryStream> ListBlobsAsMemoryStreams(string containerName, string prefix)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var memoryStreams = new List<MemoryStream>();
IEnumerable<BlobItem> blobs = containerClient.GetBlobs(prefix: prefix);
// Itera e mostra i blob trovati
System.Collections.IEnumerator enumerator = blobs.GetEnumerator();
while (enumerator.MoveNext())
{
BlobItem blobItem = enumerator.Current as BlobItem;
if (blobItem != null)
{
var blobClient = containerClient.GetBlobClient(blobItem.Name);
if (blobClient.Exists())
{
var memoryStream = new MemoryStream();
blobClient.DownloadTo(memoryStream);
memoryStream.Position = 0; // Reset position for reading
memoryStreams.Add(memoryStream);
}
}
}
return memoryStreams;
}
catch (Exception ex)
{
throw new Exception($"Errore nel listare i blob come MemoryStream: {ex.Message}");
}
}
public List<BlobClient> ListBlobsAsBlobClient(string containerName, string prefix)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClients = new List<BlobClient>();
IEnumerable<BlobItem> blobs = containerClient.GetBlobs(prefix: prefix);
// Itera e mostra i blob trovati
System.Collections.IEnumerator enumerator = blobs.GetEnumerator();
while (enumerator.MoveNext())
{
BlobItem blobItem = enumerator.Current as BlobItem;
if (blobItem != null)
{
var blobClient = containerClient.GetBlobClient(blobItem.Name);
if (blobClient.Exists())
{
blobClients.Add(blobClient);
}
}
}
return blobClients;
}
catch (Exception ex)
{
throw new Exception($"Errore nel listare i blob come BlobClient: {ex.Message}");
}
}
/// <summary>
/// Ottiene le proprietà di un blob
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>Stringa con le proprietà del blob</returns>
public string GetBlobProperties(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
if (!blobClient.Exists())
{
throw new Exception($"Blob non trovato: {blobName}");
}
var properties = blobClient.GetProperties();
var props = properties.Value;
return $"ContentType: {props.ContentType}, " +
$"ContentLength: {props.ContentLength}, " +
$"LastModified: {props.LastModified}, " +
$"ETag: {props.ETag}";
}
catch (Exception ex)
{
throw new Exception($"Errore nel recuperare le proprietà del blob: {ex.Message}");
}
}
/// <summary>
/// Scarica un blob come MemoryStream
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>MemoryStream contenente i dati del blob</returns>
public MemoryStream DownloadBlobAsMemoryStream(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
if (!blobClient.Exists())
{
throw new Exception($"Blob non trovato: {blobName}");
}
var memoryStream = new MemoryStream();
blobClient.DownloadTo(memoryStream);
memoryStream.Position = 0; // Reset position per la lettura
return memoryStream;
}
catch (Exception ex)
{
throw new Exception($"Errore nel download del blob come MemoryStream: {ex.Message}");
}
}
/// <summary>
/// Scarica un blob come array di byte
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>Array di byte contenente i dati del blob</returns>
public byte[] DownloadBlobAsBytes(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
if (!blobClient.Exists())
{
throw new Exception($"Blob non trovato: {blobName}");
}
using (var memoryStream = new MemoryStream())
{
blobClient.DownloadTo(memoryStream);
return memoryStream.ToArray();
}
}
catch (Exception ex)
{
throw new Exception($"Errore nel download del blob come byte array: {ex.Message}");
}
}
/// <summary>
/// Carica un blob da MemoryStream
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <param name="stream">MemoryStream contenente i dati da caricare</param>
/// <returns>True se il caricamento è riuscito</returns>
public bool UploadBlobFromMemoryStream(string containerName, string blobName, MemoryStream stream)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
stream.Position = 0; // Reset position per la lettura
blobClient.Upload(stream, overwrite: true);
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nel caricamento del blob da MemoryStream: {ex.Message}");
}
}
/// <summary>
/// Carica un blob da array di byte
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <param name="data">Array di byte contenente i dati da caricare</param>
/// <returns>True se il caricamento è riuscito</returns>
public bool UploadBlobFromBytes(string containerName, string blobName, byte[] data)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
using (var stream = new MemoryStream(data))
{
blobClient.Upload(stream, overwrite: true);
}
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nel caricamento del blob da byte array: {ex.Message}");
}
}
/// <summary>
/// Ottiene la dimensione di un blob in byte
/// </summary>
/// <param name="containerName">Nome del container</param>
/// <param name="blobName">Nome del blob</param>
/// <returns>Dimensione del blob in byte</returns>
public long GetBlobSize(string containerName, string blobName)
{
try
{
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
if (!blobClient.Exists())
{
throw new Exception($"Blob non trovato: {blobName}");
}
var properties = blobClient.GetProperties();
return properties.Value.ContentLength;
}
catch (Exception ex)
{
throw new Exception($"Errore nel recuperare la dimensione del blob: {ex.Message}");
}
}
/// <summary>
/// Copia un blob
/// </summary>
/// <param name="sourceContainerName">Container sorgente</param>
/// <param name="sourceBlobName">Nome blob sorgente</param>
/// <param name="destinationContainerName">Container destinazione</param>
/// <param name="destinationBlobName">Nome blob destinazione</param>
/// <returns>True se la copia è riuscita</returns>
public bool CopyBlob(string sourceContainerName, string sourceBlobName,
string destinationContainerName, string destinationBlobName)
{
try
{
var sourceContainerClient = _blobServiceClient.GetBlobContainerClient(sourceContainerName);
var sourceBlobClient = sourceContainerClient.GetBlobClient(sourceBlobName);
var destinationContainerClient = _blobServiceClient.GetBlobContainerClient(destinationContainerName);
var destinationBlobClient = destinationContainerClient.GetBlobClient(destinationBlobName);
if (!sourceBlobClient.Exists())
{
throw new Exception($"Blob sorgente non trovato: {sourceBlobName}");
}
destinationBlobClient.StartCopyFromUri(sourceBlobClient.Uri);
return true;
}
catch (Exception ex)
{
throw new Exception($"Errore nella copia del blob: {ex.Message}");
}
}
}
}
🧩How to Use in Dynamics 365 F&O (X++)
Once the helper DLL is compiled and referenced from D365FO (via Visual Studio), you can call it from X++.
Here’s a full example that:
- Lists blobs by prefix.
- Downloads each blob.
- Stores it in D365FO using
SharedServiceUnitStorage. - Deletes the blob from Azure after processing.
AzureBlobStorageHelper azureHelper = new AzureBlobStorageHelper("azureStorageConnectionString");
info('Azure Storage Helper initialized');
System.Collections.IEnumerable blobMemoryList = azureHelper.ListBlobsAsBlobClient("azureStorageContainerName", "azureStorageContainerPrefix");
System.Collections.IEnumerator lstBlobEnumerator = blobMemoryList.GetEnumerator();
while (lstBlobEnumerator.MoveNext())
{
BlobClient blobClientImp = lstBlobEnumerator.Current as BlobClient;
using (var dataStream = new System.IO.MemoryStream())
{
blobClientImp.DownloadTo(dataStream);
dataStream.Position = 0; // Reset the stream position to the beginning
StorageContext storageContext = SharedServiceUnitStorage::GetDefaultStorageContext();
SharedServiceUnitStorage blobStorageService = new SharedServiceUnitStorage(storageContext);
var blobInfo = new SharedServiceUnitStorageData();
blobInfo.Id = guid2Str(newGuid());
blobInfo.Category = destinationStorageFolder;
blobInfo.Name = System.IO.Path::GetFileName(blobClientImp.Name);
blobInfo.Accessibility = Accessibility::Public;
blobInfo.Retention = Retention::Permanent;
blobStorageService.UploadData(blobInfo, dataStream);
info(strFmt("Upload %1 XML succeeded.", System.IO.Path::GetFileName(blobClientImp.Name)));
// Delete the blob after processing
str azureStorageContainerNameStr = azureStorageContainerName;
azureHelper.DeleteBlob(azureStorageContainerNameStr, blobClientImp.Name);
}
}
🔄Replacing Deprecated APIs
Microsoft officially deprecated the Microsoft.Azure.Storage.Blob and WindowsAzure.Storage libraries. The new Azure.Storage.Blobs SDK is the only one receiving security updates and feature enhancements.
| Deprecated | Use Instead |
|---|---|
CloudBlobClient | BlobServiceClient |
CloudBlobContainer | BlobContainerClient |
CloudBlockBlob | BlobClient |
With this helper, your code is already future-ready and doesn’t require rewriting once the old SDKs are fully phased out.
🚀Benefits for D365FO Developers
- Seamless integration with X++.
- Simplified .NET assembly deployment.
- No need to manage low-level HTTP requests or shared access signatures.
- Easy testing and extension in Visual Studio.
- Makes your solution Azure-native and future-proof.
📝 Conclusion
Whether you’re building integration points, document import/export pipelines, or automated processing of files in Azure from D365FO, this helper class gives you a robust foundation that aligns with Microsoft’s latest standards.