Download image from azure blob storage in winRt - azure

I now about method DownloadToStreamAsync in azure-sdk-winRT, but i can't implement this. How can i download image from blob storage, and save in to a local folder?

You can try something like below (very bare bone implementation with no error checking):
CloudStorageAccount account = new CloudStorageAccount(new StorageCredentials("accountname", "accountkey"), true);
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference("containername");
var blob = container.GetBlockBlobReference("imagename.ext");//e.g myimage.png
var file = await KnownFolders.PicturesLibrary.CreateFileAsync("imagename.ext", CreationCollisionOption.ReplaceExisting);
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
await blob.DownloadToStreamAsync(stream);
await stream.FlushAsync();
stream.Dispose();
A few comments though:
The example above writes to the "Pictures" library. You would need to ensure that your Windows 8 App has the capability defined for it.
Try looking at shared access signature functionality instead of going the account credentials route especially if you're allowing users of your app to download images from your storage account.

Related

Can't implement azure web app service access to azure storage container (blob) using MSI

I have an azure resource group which contains Web App Service and Storage with BLOB container. My web app (.NET Core) tries to retrieve and show an image from container. The container has no public access to content (access level is private). I created system assigned identity for my app and gave it Reader role in storage access control (IAM).
This is how I get access to blobs in app's code:
const string blobName = "https://storagename.blob.core.windows.net/img/Coast.jpg";
string storageAccessToken = await GetStorageAccessTokenAsync();
var tokenCredential = new TokenCredential(storageAccessToken);
var storageCredentials = new StorageCredentials(tokenCredential);
var blob = new CloudBlockBlob(new Uri(blobName), storageCredentials);
ImageBlob = blob.Uri;
GetStorageAccessTokenAsync() does this:
var tokenProvider = new AzureServiceTokenProvider();
return await tokenProvider.GetAccessTokenAsync("https://storage.azure.com/");
Then the image is displayed by
<img src="#Model.ImageBlob" />
I don't get any exceptions in my code, but image from the BLOB container isn't shown with 404 error (specified resource doesn't exist) in browser console.
When I change container's access level to "blob" (public access), app works fine and the image is displayed.
Apparently, it is something wrong with getting credentials part, but I couldn't find any working example nor detailed explanations how it actually should work.
Any help is very appreciated.
UDPATE:
Thank you all who responded. So, it seems I've got two problems here.
1) I don't get credentials properly.
I can see that "AzureServiceTokenProvider" object (Microsoft.Azure.Services.AppAuthentication) that I create, has empty property PrincipalUsed at the runtime.
My application deployed to Azure App Service, which has system managed identity and that identity (service principal) is given permissions in Azure storage (I changed permission from account Reader to Storage Blob Data Reader as was suggested).
Shouldn't it get all data needed from the current context? If not, what I can do here?
2) I use wrong method to show image, but since the app has no access to storage anyway I can't fix it yet.
But still - what is the common way to do that in my case? I mean there is no public access to storage and I use "CloudBlockBlob" to reach images.
Reader gives access to read the control plane, but not the data plane. The role you need is Storage Blob Data Reader, which gives access to read blob contents.
For more details about this, check out: https://learn.microsoft.com/en-us/azure/role-based-access-control/role-definitions#data-operations-example
When you use <img src="#Model.ImageBlob" />, no authorization header is sent in the request by the browser. In your code, you are fetching the token, but the token is not being sent in the authorization header when the image is being fetched. So, storage API thinks this is an anonymous request. This is the reason you are getting a 404.
You need to send auth code when fetching the image. This code works for me
public async Task<ActionResult> Image()
{
const string blobName = "https://storage.blob.core.windows.net/images/image.png";
string storageAccessToken = await GetStorageAccessTokenAsync().ConfigureAwait(false);
var tokenCredential = new TokenCredential(storageAccessToken);
var storageCredentials = new StorageCredentials(tokenCredential);
var blob = new CloudBlockBlob(new Uri(blobName), storageCredentials);
Stream blobStream = blob.OpenRead();
return File(blobStream, blob.Properties.ContentType, "image.png");
}
In the view, I use
<img src="/Home/Image" />
Finally, I got it to work. First of all, the part of code regarding getting token and image from Azure storage was OK. The second problem with displaying image in RazorPages application I resolved, using this code in view:
<form asp-page-handler="GetImage" method="get">
<img src="/MyPageName?handler=GetImage" />
</form>
and corresponding code in model:
public async Task<ActionResult> OnGetGetImageAsync()
{
//getting image code and returning FileContentResult
}
But I'm still thinking: whether is more simple way to do that? Something like to add image collection to the model, fill it using "OnGet..." handler and then display its content using in view. I didn't find a way to use model properties in <img> tag. Does anyone have some suggestions?

Azure blob service, randomly 404-not found errors

We have a web app which processes external callbacks. To isolate our app from the external service, we use an azure blob to store the callback data (.json) and put a message to Azure Service bus for a processing service to pick up later.
The following code is used to write the data to blob storage:
var storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("MyStorage"));
var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("containername");
var dataFileName = Guid.NewGuid().ToString();
var blockBlob = container.GetBlockBlobReference(dataFileName);
blockBlob.UploadText(data);
blockBlob.Properties.ContentType = "application/json";
blockBlob.SetProperties();
var connectionString = CloudConfigurationManager.GetSetting("serviceBusCS");
var queueName = "MyQueue";
var client = QueueClient.CreateFromConnectionString(connectionString, queueName);
var payload = new MyCustomMessage {
Id = dataFileName
};
var message = new BrokeredMessage(payload);
client.Send(message);
On the processing side, we have a web job reading the messages one at a time and retrieving the data as follow:
var storageAccount = CloudStorageAccount.Parse("MyCS");
var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("containername");
var blockBlob = container.GetBlockBlobReference(message.Id);
if (blockBlob == null) {
return;
}
if (!blockBlob.Exists()) {
return; ==> FAILS HERE
}
// Process the message here...
// Once the processing is done, delete the blob
This design works well most of the time but we get a 404-NotFound from now and then (marked with FAILED HERE above).
QUESTION
The only way this code can fail is by having two messages with the same file name i.e: the same GUID which is nearly impossible or am I missing something?
Any idea why the blob data can't be found?
EDIT 1
Searching for the missing blob from the Azure portal shows that the blob is actually not there.
Shouldn't blockBlob.UploadText(data); throw if it fails to write data to the blob container?
EDIT 2
Thanks to Jason for indicating where to look. We crawled our logs and found that the blob is getting written successfully. The web job kicks off and processes the message but one minute later, exactly one minute later we see the web job kicking off trying to process the very same message again and it won't find the blob which is expected as the first run cleared the blob and deleted it.
If the client application receives an HTTP 404 (Not found) message from the server, this implies that the object the client was attempting to use (such as an entity, table, blob, container, or queue) does not exist in the storage service. There are a number of possible reasons for this, such as:
•The client or another process previously deleted the object
•A Shared Access Signature (SAS) authorization issue
•Client-side JavaScript code does not have permission to access the object
•Network failure
See Storage Monitoring, Diagnosing and Troubleshooting Guide for more information.

Download or View file from Azure Blob in Aurelia UI

I have my files stored in the Azure. I want to download or viewing mechanism the file on the client side. Like this:
Azure -> Api -> Client UI (Aurelia)
I have seen lot of c# examples, however I am not sure how to get the file on the UI side. Can anyone please help!
Thanks!
Edit:
Api Code:
public string getUtf8Text()
{
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var containerName = "myContainer";
var blobName = "myBlobName.pdf";
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName);
string text;
using (var memoryStream = new MemoryStream())
{
await blockBlob.DownloadToStreamAsync(memoryStream);
text = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
return text;
}
}
Trying to download a file, from the utf8 byte string. The client side code is:
var byteCharacters =result.byteArray;
var byteNumbers = new Array(result.byteArray.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var octetStreamMime = "application/octet-stream";
var contentType = octetStreamMime;
var blob = new Blob([byteArray] {type: contentType});
FileSaver.saveAs(blob, result.blobName);
it works sometimes for pdf, rest of the times its just blank pages. It hangs forever for mp4. Any idea whats going on here?
Each blob has a unique URL address. You can use this to display the contents of the blob via a client that can process a URL.
The blob URL will be similar to:
https://myaccount.blob.core.windows.net/mycontainer/myblob
See Naming and Referencing Containers, Blobs, and Metadata for more information.
The greater challenge comes in how you authenticate access to the blob for your users. You have a couple of options:
You can make blobs in the container public, and thus available for anonymous access, without authentication. This means that all blobs in that container will be public. See Manage anonymous read access to containers and blobs.
You can use a shared access signature to delegate access to blobs in the container with the permissions you specify and over the time interval that you specify. This gives you a greater degree of control than anonymous access but also requires more design effort. See Shared Access Signatures, Part 1: Understanding the SAS model.
Note that although anyone possessing your account key can authenticate and access blobs in your account, you should not share your account key with anyone. However, as the account owner, you can access your blobs from your application using authentication with the account key (also known as shared key authentication).

How can I get an Azure CloudBlockBlob from a storage URL with a SAS?

I have am trying to refactor our MVC code which has a lot of pages which make use of download url which point at a blob with a SAS. It would be great to be able to pass the Url to the controller and use it to locate the associated Blob. E.g. Have an action that has the download Url as its only input parameter. I can also create a link helper that only shows the delete link if the SAS exposes delete etc.
It would be a great help if I could pass the Url to Azure and get a CloudBlockBlob in return. So I could delete it, update it, get metadata etc.
The only way I can do it presently is resorting to using techniques like
var deleteBlobRequest = BlobRequest.Delete(new Uri(fileUrl), 30, null, DeleteSnapshotsOption.IncludeSnapshots, "");
deleteBlobRequest.GetResponse().Close();
This works but it seems very odd.
I can't figure out the code to get a CloudBlockBlob from the Uri.
Any ideas? I am presently using Azure Storage 1.7
You don't have to do anything special. If you construct a blob with a SAS Uri, storage client library takes care of this for you. For example, take this code:
CloudBlockBlob cloudBlockBlob = new CloudBlockBlob("http://127.0.0.1:10000/devstoreaccount1/temp/sastest.txt?sr=b&st=2013-01-25T04%3A28%3A09Z&se=2013-01-25T05%3A28%3A09Z&sp=rwd&sig=jIWWFwZ6MXaL6FD%2F2%2FpqPl1g4f0ElFrr1fKNg5U%2FAkg%3D");
cloudBlockBlob.Delete();
This would work just fine.
Here is the code to get the permissions of a SAS key (supposing the blobUrl is an url with the SAS key):
// Get permssions for current SAS key.
var queryString = HttpUtility.ParseQueryString(blobUrl);
var permissionsText = queryString["sp"];
var permissions = SharedAccessBlobPermissions.None;
if (permissionsText.Contains("w"))
permissions = permissions | SharedAccessBlobPermissions.Write;
if (permissionsText.Contains("r"))
permissions = permissions | SharedAccessBlobPermissions.Read;
if (permissionsText.Contains("d"))
permissions = permissions | SharedAccessBlobPermissions.Delete;
if (permissionsText.Contains("l"))
permissions = permissions | SharedAccessBlobPermissions.List;
And this will get an ICloudBlob based on an URL with SAS key (supposing the blobUrl is an url with the SAS key):
// Get the blob reference.
var blobUri = new Uri(blobUrl);
var path = String.Format("{0}{1}{2}{3}", blobUri.Scheme, Uri.SchemeDelimiter, blobUri.Authority, blobUri.AbsolutePath);
var blobClient = new CloudBlobClient(new Uri(path), new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(blobUri.Query));
ICloudBlob blobReference = blobClient.GetBlobReferenceFromServer(new Uri(path));

How to use SharedAccessSignature to access blobs

I am trying to access a blob stored in a private container in Windows Azure. The container has a Shared Access Signature but when I try
to access the blob I get a StorgeClientException "Server failed to authenticate the request. Make sure the Authorization header is formed
correctly including the signature".
The code that created the container and uploaded the blob looks like this:
// create the container, set a Shared Access Signature, and share it
// first this to do is to create the connnection to the storage account
// this should be in app.config but as this isa test it will just be implemented
// here:
// add a reference to Microsoft.WindowsAzure.StorageClient
// and Microsoft.WindowsAzure.StorageClient set up the objects
//storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["ConnectionString"]);
blobClient = storageAccount.CreateCloudBlobClient();
// get a reference tot he container for the shared access signature
container = blobClient.GetContainerReference("blobcontainer");
container.CreateIfNotExist();
// now create the permissions policy to use and a public access setting
var permissions = container.GetPermissions();
permissions.SharedAccessPolicies.Remove("accesspolicy");
permissions.SharedAccessPolicies.Add("accesspolicy", new SharedAccessPolicy
{
// this policy is live immediately
// if the policy should be delatyed then use:
//SharedAccessStartTime = DateTime.Now.Add(T); where T is some timespan
SharedAccessExpiryTime =
DateTime.UtcNow.AddYears(2),
Permissions =
SharedAccessPermissions.Read | SharedAccessPermissions.Write
});
// turn off public access
permissions.PublicAccess = BlobContainerPublicAccessType.Off;
// set the permission on the ocntianer
container.SetPermissions(permissions);
var sas = container.GetSharedAccessSignature(new SharedAccessPolicy(), "accesspolicy");
StorageCredentialsSharedAccessSignature credentials = new StorageCredentialsSharedAccessSignature(sas);
CloudBlobClient client = new CloudBlobClient(storageAccount.BlobEndpoint,
new StorageCredentialsSharedAccessSignature(sas));
CloudBlob sasblob = client.GetBlobReference("blobcontainer/someblob.txt");
sasblob.UploadText("I want to read this text via a rest call");
// write the SAS to file so I can use it later in other apps
using (var writer = new StreamWriter(#"C:\policy.txt"))
{
writer.WriteLine(container.GetSharedAccessSignature(new SharedAccessPolicy(), "securedblobpolicy"));
}
The code I have been trying to use to read the blob looks like this:
// the storace credentials shared access signature is copied directly from the text file "c:\policy.txt"
CloudBlobClient client = new CloudBlobClient("https://my.azurestorage.windows.net/", new StorageCredentialsSharedAccessSignature("?sr=c&si=accesspolicy&sig=0PMoXpht2TF1Jr0uYPfUQnLaPMiXrqegmjYzeg69%2FCI%3D"));
CloudBlob blob = client.GetBlobReference("blobcontainer/someblob.txt");
Console.WriteLine(blob.DownloadText());
Console.ReadLine();
I can make the above work by adding the account credentials but that is exactly what I'm trying to avoid. I do not want something
as sensitive as my account credentials just sitting out there and I have no idea on how to get the signature into the client app without having the account credentials.
Any help is greatly appreciated.
Why this?
writer.WriteLine(container.GetSharedAccessSignature(new SharedAccessPolicy(), "securedblobpolicy"));
and not writing the sas string you already created?
It's late and I could easily be missing something but it seems that you might not be saving the same access signature that you're using to write the file in the first place.
Also perhaps not relevant here but I believe there is a limit on the number of container-wide policies you can have. Are you uploading multiple files to the same container with this code and creating a new container sas each time?
In general I think it would be better to request a sas for an individual blob at the time you need it with a short expiry time.
Is "my.azurestorage.windows.net" just a typo? I would expect something there like "https://account.blob.core.windows.net".
Otherwise the code looks pretty similar to the code in http://blog.smarx.com/posts/shared-access-signatures-are-easy-these-days, which works.

Resources