Skip to content

Instantly share code, notes, and snippets.

@GFoley83
Forked from trailmax/AzureStorageApi
Created December 14, 2015 03:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GFoley83/0e5c258a93d7741f406d to your computer and use it in GitHub Desktop.
Save GFoley83/0e5c258a93d7741f406d to your computer and use it in GitHub Desktop.
Uploading large files to Azure Blob Storage via REST API. Code for blog post http://tech.trailmax.info/2014/01/uploading-large-files-to-azure-blob-storage-through-rest-api/
public class AzureStorageApi
{
private const string AzureApiVersion = "2012-02-12";
private const int BlockSize = 4 * 1024 * 1024;
public void UploadFile(string fullFilePath, string blobSasUri, Dictionary<string, string> metadata = null, int timeout = 60)
{
var blocks = new List<String>();
var tasks = new List<Task>();
// Cancel Signal Source
var cancelSignal = new CancellationTokenSource();
using (var fileStream = new FileStream(fullFilePath, FileMode.Open, FileAccess.Read))
{
var buffer = new byte[BlockSize];
var bytesRead = 0;
var blockNumber = 0;
while ((bytesRead = fileStream.Read(buffer, 0, BlockSize)) > 0)
{
var actualBytesRead = new byte[bytesRead];
// copy from old array to new one.
// Need this to be a separate array because we are passing that to a task
Buffer.BlockCopy(buffer, 0, actualBytesRead, 0, bytesRead);
var blockId = blockNumber++.ToString().PadLeft(10, '0');
blocks.Add(blockId);
// here could've used Task.Factory.StartNew(), but this reads better
var task = new Task(() => UploadBlock(blobSasUri, blockId, actualBytesRead, timeout), cancelSignal.Token);
task.Start();
tasks.Add(task);
}
}
try
{
// showing off here - chaining the tasks together. Don't need it here, but trying it out
var continuation = Task.Factory.ContinueWhenAll(tasks.ToArray(),
(t) => CommitAllBlocks(blobSasUri, blocks, metadata, timeout));
continuation.Wait();
}
catch (AggregateException exception)
{
// when one of the tasks fail, we want to abort all other tasks, otherwise they will keep uploading.
// This is how we signal the cancellation to all other tasks
cancelSignal.Cancel();
// AggregateException.InnerException contains the actual exception that was thrown by a task.
throw exception.InnerException;
}
}
private void UploadBlock(String sasUrl, String blockId, byte[] contentBytes, int timeout)
{
using (var client = GetHttpClient(timeout))
{
var blockUrl = String.Format("{0}&comp=block&blockid={1}", sasUrl, blockId.EncodeToBase64String());
HttpContent content = new ByteArrayContent(contentBytes);
content.Headers.ContentLength = contentBytes.Length;
content.Headers.ContentMD5 = MD5.Create().ComputeHash(contentBytes);
var result = client.PutAsync(blockUrl, content);
ProcessResult(result);
}
}
private void CommitAllBlocks(string sasUrl, List<string> blockIds, Dictionary<string, string> metadata, int timeout)
{
using (var client = GetHttpClient(timeout))
{
var contentBuilder = new StringBuilder();
contentBuilder.AppendLine(@"<?xml version=""1.0"" encoding=""utf-8""?>");
contentBuilder.AppendLine("<BlockList>");
foreach (var blockId in blockIds)
{
contentBuilder.AppendFormat("<Uncommitted>{0}</Uncommitted>", blockId.EncodeToBase64String() );
}
contentBuilder.AppendLine("</BlockList>");
var contentString = contentBuilder.ToString();
HttpContent content = new StringContent(contentString);
content.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Text.Plain);
content.Headers.ContentLength = contentString.Length;
foreach (var pair in metadata ?? new Dictionary<string, string>())
{
content.Headers.Add("x-ms-meta-" + pair.Key, pair.Value);
}
var commitUrl = String.Format("{0}&comp=blocklist", sasUrl);
var result = client.PutAsync(commitUrl, content);
ProcessResult(result);
}
}
private HttpClient GetHttpClient(int timeout)
{
var client = new HttpClient();
client.Timeout = GetTimeout(timeout);
client.DefaultRequestHeaders.Add("x-ms-date", DateTime.Now.ToUniversalTime().ToString("r"));
client.DefaultRequestHeaders.Add("x-ms-version", AzureApiVersion);
return client;
}
private TimeSpan GetTimeout(int specifiedTimeout)
{
if (specifiedTimeout == 0)
{
return TimeSpan.FromMilliseconds(Timeout.Infinite);
}
return TimeSpan.FromMinutes(specifiedTimeout);
}
private HttpResponseMessage ProcessResult(Task<HttpResponseMessage> task)
{
HttpResponseMessage response = null;
try
{
response = task.Result;
}
catch (Exception exception)
{
var innerExceptionMessage = exception.InnerException != null
? exception.InnerException.Message
: "No Inner Exception";
var message = String.Format("Unable to finish request. Application Exception: {0}; With inner exception: {1}", exception.Message, innerExceptionMessage);
throw new ApplicationException(message);
}
if (response.IsSuccessStatusCode)
{
return response;
}
var exceptionMessage = String.Format("Unable to finish request. Server returned status: {0}; {1}", response.StatusCode, response.ReasonPhrase);
throw new ApplicationException(exceptionMessage);
}
}
public static class StringExtensions
{
public static string EncodeToBase64String(this string original)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(original));
}
public static string DecodeFromBase64String(this string original)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(original));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment