Zoo 105 Podcast: adding Azure Blob storage

Zoo 105 Podcast: adding Azure Blob storage

In this last step of my application, I wanted to save the podcast episodes (the real mp3 files) in an Azure Blob storage. I didn't want to slow down my optimized Azure Function, so I decided to download the mp3 files in a separate function.

How to invoke this second function? The first idea was to invoke it automatically when saving in CosmosDB (there is a CosmosDB trigger), but this solution is not fault resistant (or at least: you need to implement fault resistance yourself).

Azure provides fault resistance out-of-the-box with queues, so I chose to use them. Azure storage is already there when creating a Function, so I needed only to create a function to create the queue (if not existing):

public static async Task<CloudQueue> GetAzureQueueAsync(IConfigurationRoot config)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(config["AzureWebJobsStorage"]);
    CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
    CloudQueue queue = queueClient.GetQueueReference(queueName);
    await queue.CreateIfNotExistsAsync();
    return queue;
}

Once I have a queue, it's easy to add items:

public static async Task EnqueueItemAsync(CloudQueue queue, Podcast2Download episode)
{
    string serializedObj = JsonConvert.SerializeObject(episode);
    CloudQueueMessage message = new CloudQueueMessage(serializedObj);
    await queue.AddMessageAsync(message);
}

Item retrieval and eventual re-enqueuing in case of errors is automatically managed by Azure; all you need is to write a Function that has the queue trigger:

[FunctionName("Download2Blob")]
public static async Task Run([QueueTrigger("podcast2download", Connection = "AzureWebJobsStorage")]
    string myQueueItem, ILogger logger, Microsoft.Azure.WebJobs.ExecutionContext context)
{
    logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}");

    ...

    Podcast2Download episode2download = AzureQueueHelper.DeserializeItem(myQueueItem);

    ...
}

UPDATE: as described here, in Azure Functions v2 the QueueTriggerAttribute requires the NuGet package Microsoft.Azure.WebJobs.Extensions.Storage.

Finally, after having downloaded the file, to save it in an Azure Blob, you need to get a container:

public static CloudBlobContainer GetBlobContainer(IConfigurationRoot config)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(config["AzureWebJobsStorage"]);
    CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(blobContainerName);
    return cloudBlobContainer;
}

With the container, you can call multiple methods to manage its files. In my case, I wanted to only upload new files:

public static async Task<long> StoreFileAsync(CloudBlobContainer cloudBlobContainer, DateTime dateUtc, string fileName, Stream stream)
{
    // If the container does't exist, create it
    if (!await cloudBlobContainer.ExistsAsync())
        try { await cloudBlobContainer.CreateAsync(); } catch { }

    string pathFileName = ...;
    CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(pathFileName);
    await cloudBlockBlob.UploadFromStreamAsync(stream);

    return stream.Position;
}

The source code for this step is available in the dedicated GitHub repository, under the tag 3.AzureBlobStorage.