I have been researching this for quite some time and I am stuck at being able to do basic operations on my storage account from my .NET Core webapi.
You should know the following:
- Allow Blob public access is set to "Disabled" (Strongly recommended by Microsoft)
- Account type is BlobStorage
- Authorize requests to Azure Storage must be done through a shared key (I do not prefer SAS)
I have referenced the package Azure.Storage.Blobs in my project and injected it as a service like the following:
services.AddSingleton(x => new BlobServiceClient(config.GetValue<string>("AzureBlobStorage")));
The AzureBlobStorage is my connection string to the storage account.
I also created a blob service and I put the following methods in
Task<Uri> UploadBlobAsync(string blobContainerName, Stream content, string contentType, string fileName);
Task<CustomBlobInfo> GetBlobAsync(string blobContainerName, string fileName);
Task<IEnumerable<string>> ListBlobsAsync(string blobContainerName);
Task DeleteBlobAsync(string blobContainerName, string fileName);
The process of uploading a blob starts as the following:
I hit the following http request (Method:POST) in my post man
http://localhost:5000/api/blob/container/6735D6F0-8FC1-4C6E-B08C-FFBB5B29C4A1
The uniqueidentifier is the container name in my storage account.
In my controller, I have the following logic that calls
IFormFile file = Request.Form.Files[0]; if (file == null) { return BadRequest(); } string fileName = Guid.NewGuid().ToString().ToLower(); string containerName = blobContainerName.ToLower(); var result = await this.blobService.UploadBlobAsync( containerName, file.OpenReadStream(), file.ContentType, fileName); var toReturn = result.AbsoluteUri; return Ok(new { path = toReturn });The call hits my
UploadBlobAsyncmethod and pass the arguments.. so far so good!
However, I run into a problem where my code throws an exception saying that public access is prohibited
public async Task<Uri> UploadBlobAsync(string blobContainerName, Stream content, string contentType, string fileName)
{
var containerClient = GetContainerClient(blobContainerName); // <<<<< EXCEPTION
var blobClient = containerClient.GetBlobClient(fileName);
await blobClient.UploadAsync(content, new BlobHttpHeaders { ContentType = contentType });
return blobClient.Uri;
}
My GetContainerClient() is a private method that I use to check if the container exists or not, if it doesn't, then create a container
private BlobContainerClient GetContainerClient(string blobContainerName)
{
var containerClient = this.blobServiceClient.GetBlobContainerClient(blobContainerName);
containerClient.CreateIfNotExists(PublicAccessType.Blob);
return containerClient;
}
So far, it is obvious why I am getting this exception.. because the public access level is disabled..
Microsoft documentation says that I need to generate a shared access key that somehow gets attached to my request..
This is why I have another method that generate the appropriate request headers as the following:
private void GenerateSharedAccessKeyRequestHeader(string blobContainerName, Stream content, string fileName, string contentType)
{
string storageKey = "key";
string storageAccount = "name";
string method = "PUT";
long contentLength = content.Length;
string requestUri = $"https://{storageAccount}.blob.core.windows.net/{blobContainerName}/{fileName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
string now = DateTime.UtcNow.ToString("R");
request.Method = method;
request.ContentType = contentType;
request.ContentLength = contentLength;
request.Headers.Add("x-ms-version", "2015-12-11");
request.Headers.Add("x-ms-date", now);
request.Headers.Add("x-ms-blob-type", "BlockBlob");
request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, storageAccount, storageKey, blobContainerName, fileName));
}
private string AuthorizationHeader(string method, string now, HttpWebRequest request, string storageAccount, string storageKey, string containerName, string blobName)
{
string headerResource = $"x-ms-blob-type:BlockBlob\nx-ms-date:{now}\nx-ms-version:2015-12-11";
string urlResource = $"/{storageAccount}/{containerName}/{blobName}";
string stringToSign = $"{method}\n\n\n{request.ContentLength}\n\n{request.ContentType}\n\n\n\n\n\n\n{headerResource}\n{urlResource}";
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(storageKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
String AuthorizationHeader = String.Format("{0} {1}:{2}", "SharedKey", storageAccount, signature);
return AuthorizationHeader;
}
The problem is I am not sure where to inject those request headers to upload my blob or to create container or even to do get/list/delete a blob from my storage account?
public async Task<CustomBlobInfo> GetBlobAsync(string blobContainerName, string fileName)
{
var containerClient = GetContainerClient(blobContainerName);
var blobClient = containerClient.GetBlobClient(fileName);
var blobDownloadInfo = await blobClient.DownloadAsync();
return new CustomBlobInfo(blobDownloadInfo.Value.Content, blobDownloadInfo.Value.ContentType);
}
public async Task<IEnumerable<string>> ListBlobsAsync(string blobContainerName)
{
var containerClient = GetContainerClient(blobContainerName);
var items = new List<string>();
await foreach (var item in containerClient.GetBlobsAsync())
{
items.Add(item.Name);
}
return items;
}
public async Task DeleteBlobAsync(string blobContainerName, string fileName)
{
var containerClient = GetContainerClient(blobContainerName);
var blobClient = containerClient.GetBlobClient(fileName);
await blobClient.DeleteIfExistsAsync();
}
This should be easy but for some reason, I have been stuck in this for days..