[!INCLUDE storage-selector-blob-include]
This tutorial will show you how to use Azure Blob Storage with ASP.NET and Visual Studio 2017. It will show how to upload, list, download, and delete blobs in the context of a simple Web API project that works with images.
To begin, clone the sample project from GitHub. The rest of the tutorial will work with this project.
If you’re unfamiliar with git, you can also download the project as a zip file. In the GitHub repository, there is a green button called Clone or download. You can click that and select Download ZIP:
Ensure that you have Visual Studio 2017 installed with the ASP.NET and web development and Azure Development workloads. This will give you all of the tools that you will need for this tutorial.
First, start the Azure Storage Emulator by pressing the Windows key on your keyboard and searching for Azure Storage Emulator:
This will start the Azure Storage Emulator on your machine, and open a command prompt which you can use to control it from there.
Open the sample application in Visual Studio 2017. Start it by pressing f5 in Visual Studio. This will launch the web app on http://localhost:58673/
so that you can interact with it or click the API tab to browse the reference.
Next, enter the following in your browser to test that your GET API is working:
http://localhost:58673/api/blobs
You should see a response that is similar to the following (if viewed as JSON):
[
"Block blob with name 'Awesome-Mountain-River-Wallpaper.jpg', content type 'application/octet-stream', size '1465150', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/Awesome-Mountain-River-Wallpaper.jpg'",
"Block blob with name 'Beautiful Stream Desktop Wallpapers (2).jpg', content type 'application/octet-stream', size '382544', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/Beautiful Stream Desktop Wallpapers (2).jpg'",
"Block blob with name 'Forest-river-wallpaper-Hd.jpg', content type 'application/octet-stream', size '2690689', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/Forest-river-wallpaper-Hd.jpg'",
"Block blob with name 'Grand-Canyon-Colorado-River.jpg', content type 'application/octet-stream', size '261788', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/Grand-Canyon-Colorado-River.jpg'",
"Block blob with name 'Smith-River.jpg', content type 'application/octet-stream', size '519207', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/Smith-River.jpg'",
"Block blob with name 'grand-canyon-colorado-river-1.jpg', content type 'application/octet-stream', size '232356', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/grand-canyon-colorado-river-1.jpg'",
"Block blob with name 'ws_Columbia_Mountain_River_Grass_1366x768.jpg', content type 'application/octet-stream', size '450769', and URI 'http://127.0.0.1:10000/devstoreaccount1/quickstart/ws_Columbia_Mountain_River_Grass_1366x768.jpg'"
]
The following sections will walk through the basics of interacting with blob storage within an ASP.NET Web API project:
These five basic operations should be enough for you to do a large amount of tasks with Blob Storage.
In the constructor, a Storage Account and Container are created as follows:
private readonly string CONN_STRING_SETTING = "AzureStorageConnectionString";
private readonly CloudBlobClient _client;
private readonly CloudBlobContainer _container;
// Initialize this controller with storage account and blob container
public BlobsController()
{
var connString = CloudConfigurationManager.GetSetting(CONN_STRING_SETTING);
var account = CloudStorageAccount.Parse(connString);
_client = account.CreateCloudBlobClient();
_container = _client.GetContainerReference(CONTAINER_NAME);
}
Every blob in Azure Storage must reside in a container. The container name is part of how you uniquely identify any blob in Azure Storage, and is your entry point for any blob operation.
Container names must also be a valid DNS name. To learn more about containers and naming, see Naming and Referencing Containers, Blobs, and Metadata.
[Route("api/blobs/upload")]
public async Task UploadFile(string path)
{
var filePathOnServer = Path.Combine(HostingEnvironment.MapPath(UPLOAD_PATH), path);
using (var fileStream = File.OpenRead(filePathOnServer))
{
var filename = Path.GetFileName(path); // Trim fully pathed filename to just the filename
var blockBlob = _container.GetBlockBlobReference(filename);
await blockBlob.UploadFromStreamAsync(fileStream);
}
}
The UploadFromStreamAsync
method is a general-purpose API for uploading from anything which can be exposed as a stream to a blob. There are other APIs available such as UploadTextAsync
, UploadFromFileAsync
, and UploadFromByteArrayAsync
.
[HttpGet]
[Route("api/blobs/download")]
public async Task DownloadFile(string blobName)
{
var blockBlob = _container.GetBlockBlobReference(blobName);
var downloadsPathOnServer = Path.Combine(HostingEnvironment.MapPath(DOWNLOAD_PATH), blockBlob.Name);
using (var fileStream = File.OpenWrite(downloadsPathOnServer))
{
await blockBlob.DownloadToStreamAsync(fileStream);
}
}
The DownloadToStreamAsync
method is a general-purpose API for downloading to anything that can be exposed as a stream. There are other APIs available such as DownloadTextAsync
, DownloadToFileAsync
, and DownloadToByteArrayAsync
.
public async Task Delete(string blobName)
{
var blob = _container.GetBlobReference(blobName);
await blob.DeleteIfExistsAsync();
}
DeleteIfExistsAsync
will delete a blob if it exists. It returns a Task<bool>
, where true
indicates that it successfully deleted the blob, and false
indicates that the container did not exist. In this case, we can simply ignore it.
You can also use the DeleteAsync
method, but you’ll need to handle any exceptions if the blob doesn’t exist yourself.
To list blobs in a container, call ListBlobs
on the container as done in the HTTP GET call from the API controller:
public async Task<IEnumerable<string>> Get()
{
var blobsInfoList = new List<string>();
var blobs = _container.ListBlobs(); // Use ListBlobsSegmentedAsync for containers with large numbers of files
var blobsList = new List<IListBlobItem>(blobs);
if (blobsList.Count == 0)
{
await InitializeContainerWithSampleData();
// Refresh enumeration after initializing
blobs = _container.ListBlobs();
blobsList.AddRange(blobs);
}
foreach (var item in blobs)
{
if (item is CloudBlockBlob blob)
{
var blobInfoString = $"Block blob with name '{blob.Name}', " +
$"content type '{blob.Properties.ContentType}', " +
$"size '{blob.Properties.Length}', " +
$"and URI '{blob.Uri}'";
blobsInfoList.Add(blobInfoString);
}
}
return blobsInfoList;
}
You’ve likely noticed that an is
expression is needed on the blob
item in the for
loop. This is because each blob
is of type IListBlobItem
, which is a common interface implemented by multiple blob types. This tutorial uses Block Blobs, which are the most common kinds of blobs. If you are working with Page Blobs or Blob Directories, you can cast to those types instead.
If you have a large number of blobs, you may need to use other listing APIs such as ListBlobsSegmentedAsync.
Now that you’ve run the the sample application, take a look at the basic operations on Blobs that it performs. All code for interacting with blobs is in a single file, BlobsController.cs
.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.Azure;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http;
using System.Diagnostics;
using System.IO;
using System.Web.Hosting;
namespace TutorialForStorage.Controllers
{
public class BlobsController : ApiController
{
private readonly string CONN_STRING_SETTING = "AzureStorageConnectionString";
private readonly string CONTAINER_NAME = "quickstart";
private readonly string UPLOAD_PATH = "~/Images/";
private readonly string DOWNLOAD_PATH = "~/Downloads";
private readonly CloudBlobClient _client;
private readonly CloudBlobContainer _container;
// Initialize this controller with storage account and blob container
public BlobsController()
{
var connString = CloudConfigurationManager.GetSetting(CONN_STRING_SETTING);
var account = CloudStorageAccount.Parse(connString);
_client = account.CreateCloudBlobClient();
_container = _client.GetContainerReference(CONTAINER_NAME);
}
public async Task<IEnumerable<string>> Get()
{
var blobsInfoList = new List<string>();
var blobs = _container.ListBlobs(); // Use ListBlobsSegmentedAsync for containers with large numbers of files
var blobsList = new List<IListBlobItem>(blobs);
if (blobsList.Count == 0)
{
await InitializeContainerWithSampleData();
// Refresh enumeration after initializing
blobs = _container.ListBlobs();
blobsList.AddRange(blobs);
}
foreach (var item in blobs)
{
if (item is CloudBlockBlob blob)
{
var blobInfoString = $"Block blob with name '{blob.Name}', " +
$"content type '{blob.Properties.ContentType}', " +
$"size '{blob.Properties.Length}', " +
$"and URI '{blob.Uri}'";
blobsInfoList.Add(blobInfoString);
}
}
return blobsInfoList;
}
public string Get(string name)
{
// Retrieve reference to a blob by filename, e.g. "photo1.jpg".
var blob = _container.GetBlockBlobReference(name);
var blobInfoString = $"Block blob with name '{blob.Name}', " +
$"content type '{blob.Properties.ContentType}', " +
$"size '{blob.Properties.Length}', " +
$"and URI '{blob.Uri}'";
return blobInfoString;
}
[Route("api/blobs/upload")]
public async Task UploadFile(string path)
{
var filePathOnServer = Path.Combine(HostingEnvironment.MapPath(UPLOAD_PATH), path);
using (var fileStream = File.OpenRead(filePathOnServer))
{
var filename = Path.GetFileName(path); // Trim fully pathed filename to just the filename
var blockBlob = _container.GetBlockBlobReference(filename);
await blockBlob.UploadFromStreamAsync(fileStream);
}
}
[HttpGet]
[Route("api/blobs/download")]
public async Task DownloadFile(string blobName)
{
var blockBlob = _container.GetBlockBlobReference(blobName);
var downloadsPathOnServer = Path.Combine(HostingEnvironment.MapPath(DOWNLOAD_PATH), blockBlob.Name);
using (var fileStream = File.OpenWrite(downloadsPathOnServer))
{
await blockBlob.DownloadToStreamAsync(fileStream);
}
}
public async Task Delete(string blobName)
{
var blob = _container.GetBlobReference(blobName);
await blob.DeleteIfExistsAsync();
}
public async Task InitializeContainerWithSampleData()
{
var folderPath = HostingEnvironment.MapPath(UPLOAD_PATH);
var folder = Directory.GetFiles(folderPath);
foreach (var file in folder)
{
await UploadFile(file);
}
}
}
}
To publish the application this sample application to Azure, you’ll need an Azure Storage account. The easiest way to do this is with Visual Studio Connected Services.
After you’ve added a service reference through Connected Services, it will have placed a true connection string in the appSettings
section of your Web.config
like so:
<appSettings>
<add key="AzureStorageConnectionString" value="UseDevelopmentStorage=true" />
<!-- Azure Storage: STORAGE_NAME -->
<add key="<STORAGE_NAME>_AzureStorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=STORAGE_NAME;AccountKey=<ACCOUNT_KEY_HERE>;EndpointSuffix=core.windows.net" />
</appSettings>
Replace the "UseDevelopmentStroage=true"
value with the real connection string generated by Connected Services.
Finally, you can publish to Azure!
After it creates the App Service web app, and the application is fully published, a browser window will open and you can interact with the sample app live over the internet!
Now that you’ve learned the basics of Blob storage, follow these links to learn more.