Before Amazon S3 existed, if your web application needed to handle the uploading and storing of files, you basically had the following options: put them on the web server file system, offload them to a NAS/SAN device, or shove them into the database. Each method had its own issues, but Amazon S3 came along and said, “Screw that, just store all those files in one big bucket in the cloud”, and so that’s now the norm.
Enter Azure Blob Storage on Microsoft’s Azure platform; its rival to Amazon S3. Up until spring of 2014, storing items in Azure Blob Storage was roughly 2x the cost of Amazon S3, but now that the pricing between the two platforms are identical, it makes Azure Blob Storage a much more attractive option.
So let’s say you’re using ASP.NET Web API to build a RESTful API for your app, you need endpoints that support uploading and downloading files, and you’ve decided to use Azure Blob Storage. Great, I have a solution for you.
Add Reference to Windows Azure Storage Library
First things first, use the Nuget package manager to reference the Windows Azure Storage library.
Blob Storage Config Settings
Second thing you need to do is add the following two config entries as <appSettings> in your web.config file – BlobStorageConnectionString and BlobStorageContainerName:
Models for Upload and Download
For this solution you need a couple model classes – BlobUploadModel and BlobDownloadModel – that are used to hold certain pieces of data when uploading and downloading files, respectively. Feel free to put these in the Models folder of your Web API project:
With that done, go ahead and add the two routes (one for uploading, one for downloading) to your WebApiConfig.cs file, typically located in your App_Start folder:
Next up is the BlobsController with the actions for uploading and downloading files:
A few things to note about the BlobsController:
- It calls into a BlobService that does the actual work.
- Both action methods use async Task, mainly for better support of handling large files.
- The PostBlobUpload() action:
- Supports uploading multiple files at once.
- Returns a list of BlobUploadModel.
- Has a check to force allowing only multipart form data; meaning, this endpoint only accepts files being posted to it.
- The GetBlobDownload() action:
- Takes in a unique blob ID in order to retrieve the blob; this is an ID stored in your database.
- Returns HttpResponseMessage instead of IHttpActionResult. IMPORTANT!
- Resets the position of the blob stream that gets returned from the BlobService; otherwise, the download won’t happen.
- Sets the proper response headers for content type, disposition, and length.
BlobService Interface and Implementation
Below is the IBlobService interface and its BlobService implementation class:
There are a few things to call out here:
- The async Task approach continues.
- The UploadBlobs() method:
- Supports multiple files at once.
- Creates an instance of the custom class BlobStorageUploadProvider, which gets passed to the ReadAsMultipartAsync() call to perform the actual upload of files.
- Has a TODO for you to take the data returned from Azure and store it in your own database so that you can retrieve it later through the API properly. You can see an example of this data further down this post.
- The DownloadBlob() method:
- Has a TODO for you to implement a helper method that retrieves the blob info from your database, based on the blobId that was passed in.
- Calls a static BlobHelper class to get the blob container name.
- Downloads the file asynchronously from Azure into a memory stream that gets returned as part of the BlobDownloadModel result. The closing and disposing of this memory stream is handled by the Web API framework.
Static BlobHelper Class
Here’s the small BlobHelper class, which contains a single static method for getting a reference to your blob container. Notice that it reads the BlobStorageConnectionString and BlobStorageContainerName <appSettings> from config that we defined earlier:
Now let’s look at the BlobStorageUploadProvider, which performs the actual file uploads into Azure:
Some notes about this class:
- It inherits from MultipartFileStreamProvider.
- It exposes a property named Uploads, which contains the list of files that were uploaded.
- It uses Path.GetTempPath() as the temporary location on disk to store the files before sending them to Azure.
- Also uses the static BlobHelper to get the blob container.
- It overrides ExecutePostProcessingAsync() to inject the uploading of the files to Azure, then calls the base to complete the task.
Sample JSON Output for Blob Uploads
With all that in place, you can now use those endpoints to upload and download files into Azure Blob Storage. Here’s a sample JSON output from the API when uploading two files into Azure:
Here you can see the data you should store in your own database to maintain a relationship between your application and Azure.
The download API endpoint works as you’d expect – give it the blobId from your database and hit it from a browser or tool like Postman or Fiddler; you’ll get prompted to download the requested file.
Increase Request Limits
The default request size for ASP.NET is 4MB, so you’ll need to increase that limit if you want to allow uploading and downloading of larger files. You can do this by setting the maxRequestLength and maxAllowedContentLength values in your web.config file.
NOTE: the maxRequestLength value is in kilobytes, but the maxAllowedContentLength value is in bytes.
Here’s an example for increasing request limits to 100MB:
It probably seems like there’s a lot here, but it’s actually pretty straight forward when you break it down. To help paint the bigger picture, here are two sequence diagrams that show the parts involved for uploading and downloading files through the API into Azure (click each one to open full size image):
Upload Files to Azure Blob Storage
Download File from Azure Blob Storage
I’ve spent a fair amount of time refining this design and think it will prove useful if you have similar needs. One pleasant surprise was Azure’s performance; uploading and downloading files was much quicker than I expected. Enjoy.
Featured Image: All rights reserved by Luigi R. Viggiano