In this article, we are going to see how we can download multiple files from the Azure Blob Storage using ASP.NET Core. We can say that this article is an appendix to our multiple articles that cover Azure Blob Storage, and we encourage you to read them as well.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Interacting With Blobs in ASP.NET Core

ASP.NET Core has three clients to interact with different types of resources: BlobServiceClient, BlobContainerClient, and BlobClient. A client that has methods to work with blobs is the BlobClient. Unfortunately, a method to download multiple blobs or download an entire folder doesn’t exist. Instead, we are going to perform simultaneous downloads for each blob, and we are going to do it in parallel to be somewhat performant.

How to Setup Azure Blob Storage Locally?

Instead of working with the actual Azure storage, we are going to use Azurite, an emulator used for testing and development. We are going to create a small console application with Visual Studio 2022 since Azurite comes with it by default.

Note that you can easily switch to the actual Azure storage later by changing the connection string. You can see how to set up everything on Azure as part of this article.

Let’s create a console application from Visual Studio. Since Azurite starts automatically only for ASP.NET and Azure Functions projects, we are going to start Azurite from the command line:

C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Microsoft\Azure Storage Emulator\azurite.exe

After we run the executable file, Azurite will start listening for connections:

Azurite Blob service is starting at http://127.0.0.1:10000
Azurite Blob service is successfully listening at http://127.0.0.1:10000
Azurite Queue service is starting at http://127.0.0.1:10001
Azurite Queue service is successfully listening at http://127.0.0.1:10001
Azurite Table service is starting at http://127.0.0.1:10002
Azurite Table service is successfully listening at http://127.0.0.1:10002

We are going to use Microsoft Azure Storage Explorer to connect to Azurite blob storage and to prepare some files. After the download and launch, we can find our Azurite storage under the local-1 account (since we already run it):

Azure Storage Explorer lacl-1 key

Then let’s right-click on the Blob Containers and choose Create Blob Container in the dialog to create one container called multiple-files.

Finally, we can upload 50 random files to that container.

Great, we are now ready for the coding.

Download Multiple Files From Azure Blob Storage

Let’s install the Azure Blob Storage client library for the .NET by running the command in the Package Manager Console:

Install-Package Azure.Storage.Blobs -Version 12.9.1

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Why this specific version? At the moment of writing this article, there is a bug with the latest package version and the Azurite version (3.14.1) that comes with Visual Studio. It turns out that they are not backward compatible and that we should either update Azurite to version 3.16.0 or downgrade the SDK a bit, which we decided to do.

To continue, let’s connect to our local storage in the Program class:

var connectionString = "UseDevelopmentStorage=true";
var blobServiceClient = new BlobServiceClient(connectionString);

And let’s create MultipleDownloadExamples class:

public class MultipleDownloadExamples
{
    private readonly BlobServiceClient _blobServiceClient;

    public MultipleDownloadExamples(BlobServiceClient blobServiceClient)
    {
        _blobServiceClient = blobServiceClient;
    }
}

In our examples, we are going to fetch all the files from our container. Although we have only 50 blobs, there can be thousands of them, so we will fetch them in pages.

That said, we are going to add a method in the MultipleDownloadExample class that downloads blobs in parallel:

public async Task DownloadMultipleFilesUsingParallelForEachAsync(string containerName)
{
    var blobContainerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobPages = blobContainerClient.GetBlobsAsync().AsPages();

    await Parallel.ForEachAsync(blobPages, new ParallelOptions { MaxDegreeOfParallelism = 2 }, async (blobPage, token) =>
    {
        await Parallel.ForEachAsync(blobPage.Values, new ParallelOptions { MaxDegreeOfParallelism = 2 }, async (blob, token) =>
        {
            var blobClient = blobContainerClient.GetBlobClient(blob.Name);
            using var file = File.Create(blob.Name);
            await blobClient.DownloadToAsync(file);
        });
    });
}

We utilize the Parallel.ForEachAsync method that we got in the .NET 6 version. First, the Parallel.ForEachAsync method is going through pages of blobs in parallel, and then it is downloading blobs from each page.

Lastly, we are going to call that method in our Program class:

var multipleDownloadExamples = new MultipleDownloadExamples(blobServiceClient);

// Multiple download with parallel foreach async
await multipleDownloadExamples.DownloadMultipleFilesUsingParallelForEachAsync("multiple-files");

Using the SemaphoreSlim Class as an Alternative

Sometimes, we don’t want to use the Parallel.ForEach method. For example, they don’t work as expected when used in Azure Functions.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

So, let’s add another method where we achieve parallelism in a different way:

public async Task DownloadMultipleFilesUsingSemaphoreSlim(string containerName)
{
    var blobContainerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobPages = blobContainerClient.GetBlobsAsync().AsPages();

    var semaphore = new SemaphoreSlim(4);
    var tasks = new List<Task>();

    await foreach (var blobPage in blobPages)
    {
        foreach (var blob in blobPage.Values)
        {
            await semaphore.WaitAsync();

            tasks.Add(Task.Run(async () =>
            {
                try
                {
                    var blobClient = blobContainerClient.GetBlobClient(blob.Name);
                    using var file = File.Create(blob.Name);
                    await blobClient.DownloadToAsync(file);
                }
                finally
                {
                    semaphore.Release();
                }
            }));
        }

        await Task.WhenAll(tasks);
        tasks.Clear();
    }
}

Although we don’t iterate through blob pages in parallel, we still manage to download blobs from each page in parallel, which is still great. We control the degree of parallelism by passing an initial value of the granted concurrent requests to the constructor of the SemaphoreSlim class.

Now, we can call this method as well in the Program class to download our files:

await multipleDownloadExamples.DownloadMultipleFilesUsingSemaphoreSlim("multiple-files");

Conclusion

In this article, we’ve learned how to download multiple files from Azure Blob storage. Also, we have seen how to set up Azurite, a new storage emulator used for testing and development.