In this post I briefly cover the concept of Storage Accounts and Blob Storage; however, there are more to blobs than this simple use case. In this post, I’ll explore creating a blob file from a text stream, and then adding to that file.
As is stated in the post referenced above, Azure provides a facility for storing files in, what are known as, Azure Blobs.
In order to upload a file to a blob, you need a storage account, and a container. Setting these up is a relatively straightforward process and, again, is covered in the post above.
Our application here will take the form of a simple console app that will prompt the user for some text, and then add it to the file in Azure.
Set-up
Once you’ve set-up your console app, you’ll need the Azure NuGet Storage package.
Also, add the connection string to your storage account into the app.config:
<connectionStrings>
<add name="Storage" connectionString="DefaultEndpointsProtocol=https;AccountName=testblob;AccountKey=wibble/dslkdsjdljdsoicj/rkDL7Ocs+aBuq3hpUnUQ==;EndpointSuffix=core.windows.net"/>
</connectionStrings>
Here’s the basic code for the console app:
static void Main(string[] args)
{
Console.Write("Please enter text to add to the blob: ");
string text = Console.ReadLine();
UploadNewText(text);
Console.WriteLine("Done");
Console.ReadLine();
}
I’ll bet you’re glad I posted that, otherwise you’d have been totally lost. The following snippets are possible implementations of the method UploadNewText().
Uploading to BlockBlob
The following code will upload a file to a blob container:
string connection = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;
string fileName = "test.txt";
string containerString = "mycontainer";
using (MemoryStream stream = new MemoryStream())
using (StreamWriter sw = new StreamWriter(stream))
{
sw.Write(text);
sw.Flush();
stream.Position = 0;
CloudStorageAccount storage = CloudStorageAccount.Parse(connection);
CloudBlobClient client = storage.CreateCloudBlobClient();
CloudBlobContainer container = client.GetContainerReference(containerString);
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
blob.UploadFromStream(stream);
}
(note that the name of the container in this code is case sensitive)
If we have a look at the storage account, a text file has, indeed been created:
But, what if we want to add to that? Well, running the same code again will work, but it will replace the existing file. To prove that, I’ve changed the text to “Test data 2” and run it again:
So, how do we update the file? Given that we can update it, one possibility is to download the existing file, add to it and upload it again; that would look something like this:
string connection = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;
string fileName = "test.txt";
string containerString = "mycontainer";
CloudStorageAccount storage = CloudStorageAccount.Parse(connection);
CloudBlobClient client = storage.CreateCloudBlobClient();
CloudBlobContainer container = client.GetContainerReference(containerString);
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
using (MemoryStream stream = new MemoryStream())
{
blob.DownloadToStream(stream);
using (StreamWriter sw = new StreamWriter(stream))
{
sw.Write(text);
sw.Flush();
stream.Position = 0;
blob.UploadFromStream(stream);
}
}
This obviously means two round trips to the server, which isn’t the best thing in the world. Another possible option is to use the Append Blob…
Azure Append Blob Storage
There is a blob type that allows you to add to it without actually touching it; for example:
string connection = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;
string fileName = "testAppend.txt";
string containerString = "mycontainer";
CloudStorageAccount storage = CloudStorageAccount.Parse(connection);
CloudBlobClient client = storage.CreateCloudBlobClient();
CloudBlobContainer container = client.GetContainerReference(containerString);
CloudAppendBlob blob = container.GetAppendBlobReference(fileName);
if (!blob.Exists()) blob.CreateOrReplace();
using (MemoryStream stream = new MemoryStream())
using (StreamWriter sw = new StreamWriter(stream))
{
sw.Write("Test data 4");
sw.Flush();
stream.Position = 0;
blob.AppendFromStream(stream);
}
There are a few things to note here:
-
The reason that I changed the name of the blob is that you can’t append to a BlockBlob (at least not using an AppendBlob); so it has to have been created for the purpose of appending.
-
While UploadFromStream will just create the file if it doesn’t exist, with the AppendBlob, you need to do it explicitly.
PutBlock
The final alternative here is to use PutBlock. This can bridge the gap, by allowing the addition of blocks into an existing block blob. However, you either need to maintain the Block ID list manually, or download the existing block list; here’s an example of creating, or adding to a file using the PutBlock method:
string connection = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;
string fileName = "test4.txt";
string containerString = "mycontainer";
CloudStorageAccount storage = CloudStorageAccount.Parse(connection);
CloudBlobClient client = storage.CreateCloudBlobClient();
CloudBlobContainer container = client.GetContainerReference(containerString);
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
ShowBlobBlockList(blob);
using (MemoryStream stream = new MemoryStream())
using (StreamWriter sw = new StreamWriter(stream))
{
sw.Write(text);
sw.Flush();
stream.Position = 0;
double seconds = (DateTime.Now - new DateTime(2000, 1, 1)).TotalSeconds;
string blockId = Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes(seconds.ToString()));
Console.WriteLine(blockId);
//string blockHash = GetMD5HashFromStream(bytes);
List<string> newList = new List<string>();
if (blob.Exists())
{
IEnumerable<ListBlockItem> blockList = blob.DownloadBlockList();
newList.AddRange(blockList.Select(a => a.Name));
}
newList.Add(blockId);
blob.PutBlock(blockId, stream, null);
blob.PutBlockList(newList.ToArray());
}
The code above owes a lot to the advice given on this Stack Overflow question.
In order to avoid conflicts in the Block Ids, I’ve used a count of seconds since an arbitrary date. Obviously, this won’t work in all cases. Further, it’s worth noting that the code above still does two trips to the server (it has to download the block list).
The commented MD5 hash allows you to provide some form of check on the data being valid, should you choose to use it.
What is ShowBlobBlockList(blob)?
The following function will give some details relating to the existing blocks (it is shamelessly plagiarised from here):
public static void ShowBlobBlockList(CloudBlockBlob blockBlob)
{
if (!blockBlob.Exists()) return;
IEnumerable<ListBlockItem> blockList = blockBlob.DownloadBlockList(BlockListingFilter.All);
int index = 0;
foreach (ListBlockItem blockListItem in blockList)
{
index++;
Console.WriteLine("Block# {0}, BlockID: {1}, Size: {2}, Committed: {3}",
index, blockListItem.Name, blockListItem.Length, blockListItem.Committed);
}
}
Summary
Despite being an established technology, these methods and techniques are sparsely documented on the web. Obviously, there are Microsoft docs, and they are helpful, but, unfortunately, not exhaustive.
References
http://www.c-sharpcorner.com/UploadFile/40e97e/windows-azure-blockblob-putblock-method/
https://docs.microsoft.com/is-is/rest/api/storageservices/put-block