Adventures with Azure Storage: Read/Write Files to Blob Storage from a .NET Core Web API

azure storage

I wanted to share some code I wrote recently for uploading a file to a .NET Core Web API and writing it to Blob Storage, as well as, downloading the file from the .NET Core Web API.

As a bonus, we will use SAS tokens to read and write to Blob Storage.

Let’s get started.

Create an Azure Storage account or use an existing one.

Open Visual Studio or VS Code and Create a new .NET Core Web API project.

Add a folder called Helpers.

Add two files, one called AzureStorageBlobOptions.cs and another called AzureStorageBlobOptionsTokenGenerator.cs.

Configuration will be provided by AzureStorageBlobOptions and the SAS Token will be generated by AzureStorageBlobOptionsTokenGenerator.

public class AzureStorageBlobOptions
{
    public string AccountName { get; set; }
    public string AccountKey { get; set; }
    public string ConnectionString { get; set; }
    public string FilePath { get; set; }

    public IOptions AsOptions()
    {
        return Options.Create(this);
    }
}   

public class AzureStorageBlobOptionsTokenGenerator
{
    private readonly IOptions _options;

    public AzureStorageBlobOptionsTokenGenerator(
        IOptions options)
    {
        _options = options;
    }

    public string GenerateSasToken(
        string containerName)
    {
        return this.GenerateSasToken(
            containerName,
            DateTime.UtcNow.AddSeconds(30));
    }

    public string GenerateSasToken(
        string containerName,
        DateTime expiresOn)
    {
        var cloudStorageAccount =
            CloudStorageAccount.Parse(_options.Value.ConnectionString);
        var cloudBlobClient =
            cloudStorageAccount.CreateCloudBlobClient();
        var cloudBlobContainer =
            cloudBlobClient.GetContainerReference(containerName);

        var permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write;

        string sasContainerToken;

        var shareAccessBlobPolicy =
            new SharedAccessBlobPolicy()
            {
                SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-5),
                SharedAccessExpiryTime = expiresOn,
                Permissions = permissions
            };

        sasContainerToken =
            cloudBlobContainer.GetSharedAccessSignature(shareAccessBlobPolicy, null);

        return sasContainerToken;
    }
}

In the appsettings.Development.json, Add a section called AzureStorageBlobOptions and Add configuration keys and Update the key values based on your Azure Storage account and Blob container.

"AzureStorageBlobOptions": {
    "AccountName": "AZURE_STORAGE_ACCOUNT_NAME",
    "ConnectionString": "AZURE_STORAGE_ACCOUNT_CONNECTION_STRING",
    "FilePath": "AZURE_STORAGE_ACCOUNT_BLOB_CONTAINER_NAME"
}

In the Startup.cs file add dependency injection support for the AzureStorageBlobOptions and AzureStorageBlobOptionsTokenGenerator classes.

services.Configure(
     options => Configuration.GetSection(nameof(AzureStorageBlobOptions)).Bind(options));

services.AddSingleton();

In the Controllers folder, Add a controller called FilesController.

Update the FilesController constructor to accept two parameters, one for AzureStorageBlobOptions and AzureStorageBlobOptionsTokenGenerator.

[Route("api/files")]
[ApiController]
[Produces("application/json")]
public class FilesController : ControllerBase
{
    private readonly IOptions _azureStorageBlobOptions;
    private readonly AzureStorageBlobOptionsTokenGenerator _azureStorageBlobOptionsTokenGenerator;

    public FilesController(
        IOptions azureStorageBlobOptions,
        AzureStorageBlobOptionsTokenGenerator azureStorageBlobOptionsTokenGenerator)
    {
        _azureStorageBlobOptions = azureStorageBlobOptions;
        _azureStorageBlobOptionsTokenGenerator = azureStorageBlobOptionsTokenGenerator;
    }
}

Add a PostAsync method to support file upload, you will also want to add a Consumes attribute to handle the file upload.

[HttpPost]
[Consumes("application/json", "application/json-patch+json", "multipart/form-data")]
public async Task PostAsync(
    IFormFile file)
{
    var sasToken =
        _azureStorageBlobOptionsTokenGenerator.GenerateSasToken(
            _azureStorageBlobOptions.Value.FilePath);

    var storageCredentials =
        new StorageCredentials(
            sasToken);

    var cloudStorageAccount =
        new CloudStorageAccount(storageCredentials, _azureStorageBlobOptions.Value.AccountName, null, true);

    var cloudBlobClient =
        cloudStorageAccount.CreateCloudBlobClient();

    var cloudBlobContainer =
        cloudBlobClient.GetContainerReference(
            _azureStorageBlobOptions.Value.FilePath);

    var blobName =
        $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";

    var cloudBlockBlob =
        cloudBlobContainer.GetBlockBlobReference(blobName);

    cloudBlockBlob.Properties.ContentType =
        file.ContentType;

    using (var fileStream = file.OpenReadStream())
    {
        await cloudBlockBlob.UploadFromStreamAsync(fileStream);
    }

    return Ok(new { name = blobName });
}   

Add a GetAsync method to support file download.

[HttpGet("{id}")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "")]
public async Task GetAsync(
        string id)
{
    var sasToken =
         _azureStorageBlobOptionsTokenGenerator.GenerateSasToken(
            _azureStorageBlobOptions.Value.FilePath);

    var storageCredentials =
        new StorageCredentials(
            sasToken);

    var cloudStorageAccount =
        new CloudStorageAccount(storageCredentials, _azureStorageBlobOptions.Value.AccountName, null, true);

    var cloudBlobClient =
        cloudStorageAccount.CreateCloudBlobClient();

    var cloudBlobContainer =
        cloudBlobClient.GetContainerReference(
            _azureStorageBlobOptions.Value.FilePath);

    var blobName =
        id;

    var cloudBlockBlob =
        cloudBlobContainer.GetBlockBlobReference(blobName);

    var ms = new MemoryStream();

    await cloudBlockBlob.DownloadToStreamAsync(ms);

    return File(ms.ToArray(), cloudBlockBlob.Properties.ContentType);
}   

Run the Web API.

Open Postman, and Create a new POST request.

Enter the API URL, in my case it was https://localhost:44327/api/files.

Set Body to form-data.

Add a key called file, make sure to change the type to File, defaults to Text.

Add the file to upload and Send the request.

If the call is successful, you should receive an OK response with the new name of the file uploaded.

Now to download the file.

Create a new GET request in Postman.

Enter the API URL, in my case it was https://localhost:44327/api/files/RETURN_FILE_NAME, and Send the request.

If the call is successful, you should see the image displayed in Postman.

Thanks for reading! And please feel free to share any feedback, it is much appreciated!

Leave a Reply

Your email address will not be published.