Created
January 9, 2014 02:43
-
-
Save trailmax/8328585 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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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