Azure blob storage - error displaying uploaded images - azure

I've tried lots of different approaches & read lots of answers on SO but reaching out for help with this as I've hit a wall.
I have a storage account & container on azure, I'm successfully uploading images there but for some reason they seem to be corrupting.
I cannot access any image via an img tag nor can I view them (in windows photos) if I download them directly from the container.
I believe I have the correct configuration, public access level blob (I've also tried public access level container).
container access level
view of the uploaded blob
The image doesn't display via the url on my razor page
html img tag
Nor am I able to view it if I download & open in windows
downloaded img from container
2 examples of filestream approaches I've tried below
//approach 1
using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
{
BlobClient blobClient = container.GetBlobClient(fileName);
await blobClient.UploadAsync(fileStream, new BlobHttpHeaders { ContentType = "image/jpg" });
}
//approach 2
using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
{
container.UploadBlob(attachment.FileName, fileStream);
}
//service
public async Task<bool> UploadSample(IFormFile attachment)
{
var configSection = Configuration.GetSection("AzureBlobStorge");
var connectionString = configSection.GetSection("ConnectionString").Value;
var containerName = configSection.GetSection("ContainerName").Value;
string fileName = Path.GetFileName(attachment.FileName);
string filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot\\images\\profiles", fileName);
BlobContainerClient container = new BlobContainerClient(connectionString, containerName);
container.SetAccessPolicy(PublicAccessType.BlobContainer);
using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
{
container.UploadBlob(attachment.FileName, fileStream);
//BlobClient blobClient = container.GetBlobClient(fileName);
//await blobClient.UploadAsync(fileStream, new BlobHttpHeaders { ContentType = "image/jpg" });
}
return true;
}
//controller
[HttpPost]
[ActionName("ProfilePicsAsync")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ProfilePicsAsync(Members item)
{
try
{
IFormFile formFile = HttpContext.Request.Form.Files[0];
var member = await _cosmosDbService.GetItemAsync(formFile.Name); //Id passed in the formfile object under name
if (ModelState.IsValid)
{
if (IsAdminUser())
{
if (formFile != null)
{
await _blobStorageService.UploadSample(formFile);
}
return RedirectToAction("Index");
}
return BadRequest("Not Authorised");
}
}
catch (Exception ex)
{
if(ex is BusinessRuleException)
{
return BadRequest(new BusinessRuleException(ex.Message));
}
}
//view
<form asp-action="ProfilePicsAsync" asp-controller="Home" method="post" enctype="multipart/form-data">
#Html.HiddenFor(m => m.Id)
<p>INPUT</p>
<input asp-for="Attachment" Name="#Model.Id" />
<button type="submit" id="btnUpload" class="btn btn-primary">Upload</button>
</form>

As mentioned in the comments, the issue is indeed with your upload code. Basically you're uploading a zero byte file. This is happening because of the following line of code:
using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
When you use FileMode.Create, if the file exists it will be overwritten. From the documentation here:
Specifies that the operating system should create a new file. If the
file already exists, it will be overwritten. This requires Write
permission. FileMode.Create is equivalent to requesting that if the
file does not exist, use CreateNew; otherwise, use Truncate. If the
file already exists but is a hidden file, an
UnauthorizedAccessException exception is thrown.
So essentially you're overwriting the file you want to upload with a zero byte file and then uploading that zero byte file.
To fix this, please change the above line of code to:
using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
After that both your upload and download should work.

Tutorial: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-upload-process-images?tabs=dotnet%2Cazure-powershell
Repo: https://github.com/Azure-Samples/storage-blob-upload-from-webapp
The images controller in this tutorial basically had a working solution for me, I threw out most of my code & started again.
This was the important bit
if (StorageHelper.IsImage(formFile))
{
if (formFile.Length > 0)
{
using (Stream stream = formFile.OpenReadStream())
{
isUploaded = await StorageHelper.UploadFileToStorage(stream, formFile.FileName, storageConfig);
}
}
}

Related

Azure Block Blob: "The specified block list is invalid." Microsoft.Azure.Storage.StorageException when compressing files > 2GB between Blobs

The issue happens when I upload a file to one blob (Blob1) which in turn runs a background compression service. The background service streams the file from Blob1, compresses it, and stores it as a zip file in a separate blob (Blob2) to cache for the user to download.
The process works without issue for files < 2GB, but throws a Micrososft.Azure.Storage.StorageException when the file size is > 2GB.
using Microsoft.Azure.Storage.Blob 11.2.2
Sample Code
public async void DoWork(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await _messageSemaphore.WaitAsync(cancellationToken);
MyModel model = await _queue.PeekMessage();
if(model != null)
{
try
{
//Get CloudBlockBlob zip blob reference
var zipBlockBlob = await _storageAccount.GetFileBlobReference(_configuration[ConfigKeys.ContainerName], model.Filename, model.FileRelativePath);
using (var zipStream = zipBlockBlob.OpenWrite()) //Opens zipstream
{
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false)) // Create new ZipArchive
{
//Add each file to the zip archive
foreach (var fileUri in Files)
{
var file = new Uri(fileUri);
var cloudBlockBlob = blobContainer.GetBlockBlobReference(file);
using (var blobStream = cloudBlockBlob.OpenRead())//Opens read stream
{
//Create new ZipEntry
var zipEntry = archive.CreateEntry(model.Filename, CompressionLevel.Fastest);
using (var zipEntryStream = zipEntry.Open())
{
//Zip file
blobStream.CopyTo(zipEntryStream);
}
}
}
}
}
}
catch (FileNotFoundException e)
{
Console.WriteLine($"Download Error: {e.Message}");
}
catch (Microsoft.Azure.Storage.StorageException e) // "
{
Console.WriteLine($"Storage Exception Error: {e.Message}");
}
}
else
{
_messageSemaphore.Release();
// Wait for 1 minute between polls when idle
await Task.Delay(_sleepTime);
}
}
}
The problem was with concurrency and having multiple application instances writing to the same blob at the same time. To get around this I ended up implementing an Azure blob lease on the blob being created but had to create a temp file to apply the lease to. This is a decent enough work around for now, but this could / should probably be implemented using an Azure event driven service.

azure blob client returns file not fully

i'm trying to download file from azure blob storage, but it returns only part of file. What i'm doing wrong ? File in storage is not corrupted
public async Task<byte[]> GetFile(string fileName)
{
var blobClient = BlobContainerClient.GetBlobClient(fileName);
var downloadInfo = await blobClient.DownloadAsync();
byte[] b = new byte[downloadInfo.Value.ContentLength];
await downloadInfo.Value.Content.ReadAsync(b, 0, (int)downloadInfo.Value.ContentLength);
return b;
}
I'm using Azure.Storage.Blobs 12.4.2 package. I tried this code and it works for me
public async Task<byte[]> GetFile(string fileName)
{
var blobClient = BlobContainerClient.GetBlobClient(fileName);
using (var memorystream = new MemoryStream())
{
await blobClient.DownloadToAsync(memorystream);
return memorystream.ToArray();
}
}
I am not able to full understand your code as the current BlobClient as of v11.1.1 does not expose any download methods. As #Guarav Mantri-AIS mentioned the readAsync can behave in that manner.
Consider an alternative using the DownloadToByteArrayAsync() which is part of the API. I have include the code required to connect but of course this is just for the purpose of demonstrating a full example.
Your method would be condensed as follows:
public async Task<byte[]> GetFile(string containerName, string fileName)
{
//i am getting the container here, not sure where or how you are doing this
var container = GetContainer("//your connection string", containerName);
//Get the blob first
ICloudBlob blob = container.GetBlockBlobReference(fileName);
//and now download it straight to a byte array
return await blobClient.DownloadAsync();
}
public CloudBlobContainer GetContainer(string connectionString, string containerName)
{
//1. connect to the account
var account = CloudStorageAccount.Parse(connectionString);
//2. create a client
var blobClient = _account.CreateCloudBlobClient();
//3. i am getting the container here, not sure where or how you are doing this
return = _blobClient.GetContainerReference(containerName);
}

Uploading ID to same Azure Blob Index that a file is being uploaded to using .Net SDK

I am uploading documents to my an Azure Blob Storage, which works perfect, but I want to be able to link and ID to this specifically uploaded document.
Below is my code for uploading the file:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file)
{
try
{
var path = Path.Combine(Server.MapPath("~/App_Data/Uploads"), file.FileName);
string searchServiceName = ConfigurationManager.AppSettings["SearchServiceName"];
string blobStorageKey = ConfigurationManager.AppSettings["BlobStorageKey"];
string blobStorageName = ConfigurationManager.AppSettings["BlobStorageName"];
string blobStorageURL = ConfigurationManager.AppSettings["BlobStorageURL"];
file.SaveAs(path);
var credentials = new StorageCredentials(searchServiceName, blobStorageKey);
var client = new CloudBlobClient(new Uri(blobStorageURL), credentials);
// Retrieve a reference to a container. (You need to create one using the mangement portal, or call container.CreateIfNotExists())
var container = client.GetContainerReference(blobStorageName);
// Retrieve reference to a blob named "myfile.gif".
var blockBlob = container.GetBlockBlobReference(file.FileName);
// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = System.IO.File.OpenRead(path))
{
blockBlob.UploadFromStream(fileStream);
}
System.IO.File.Delete(path);
return new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = "Success"
};
}
catch (Exception ex)
{
throw;
}
}
I have added the ClientID field to the Index(It is at the bottom), but have no idea how I am able to add this to this index. This is still al nerw to me and just need a little guidance if someone can help :
Thanks in advance.

Azure block-blob storage uploading same file in succession fails

I' m using Azure Block Blob Storage to keep my files. Here is my code to upload file.
I m calling the method twice as below for the same file in the same request;
The first call of the method saves the file as expected but the second call saves file as length of 0 so i cant display the image and no error occurs.
[HttpPost]
public ActionResult Index(HttpPostedFileBase file)
{
UploadFile(file);
UploadFile(file);
return View();
}
public static string UploadFile(HttpPostedFileBase file){
var credentials = new StorageCredentials("accountName", "key");
var storageAccount = new CloudStorageAccount(credentials, true);
var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("images");
container.CreateIfNotExists();
var containerPermissions = new BlobContainerPermissions
{
PublicAccess = BlobContainerPublicAccessType.Blob
};
container.SetPermissions(containerPermissions);
var blockBlob = container.GetBlockBlobReference(Guid.NewGuid().ToString());
blockBlob.Properties.ContentType = file.ContentType;
var azureFileUrl = string.Format("{0}", blockBlob.Uri.AbsoluteUri);
try
{
blockBlob.UploadFromStream(file.InputStream);
}
catch (StorageException ex)
{
throw;
}
return azureFileUrl ;
}
I just find the below solution which is strange like mine, but it does not help.
Strange Sudden Error "The number of bytes to be written is greater than the specified ContentLength"
Any idea?
Thanks
You need to reset the position of the stream back to the beginning. Put this line at the top of your UploadFile method.
file.InputStream.Seek(0, SeekOrigin.Begin);

windows azure upload file path error: 'Could not find file... '

I've been trying to upload a file to the Azure storage container.
When I run my web app locally, if file is not in the web site root I get an error:
Could not find file 'C:\Program Files (x86)\IIS Express\test.txt'.
If I copy the file to the web app root folder, it works fine.
Running web app on the cloud I get an error:
Could not find file 'D:\Windows\system32\test.txt'.
I can't get the full local file path from HttpPostedFileBase object.
The code:
private string UploadFile(HttpPostedFileBase file, string folder)
{
try
{
var date = DateTime.UtcNow.ToString("yyyyMMdd-hhmmss-");
var fileName = Path.GetFileName(file.FileName);
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=http;AccountName=test;AccountKey=asdfasfasdasdf");
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(folder);
bool b = container.CreateIfNotExists();
CloudBlockBlob blockBlob = container.GetBlockBlobReference(date + fileName);
blockBlob.Properties.ContentType = file.ContentType;
using (var fileStream = System.IO.File.OpenRead(fileName))
{
blockBlob.UploadFromStream(fileStream);
}
return blockBlob.Uri.AbsoluteUri;
}
catch (Exception ex)
{
return ex.Message;
}
The HttpPostedFileBase manual has this to say;
FileName Gets the fully qualified name of the file on the client.
That is, the file name is not one that can be opened on the server.
I think what you really want to do is use the InputStream property;
blockBlob.Properties.ContentType = file.ContentType;
blockBlob.UploadFromStream(file.InputStream); // Upload from InputStream
return blockBlob.Uri.AbsoluteUri;
Thank you so much: Joachim Isaksson (above) I spent the whole day yesterday fighting with this one line of code. I've voted your answer up but I thought I'd add a copy of your solution that lies within my own code now:
public async Task<ActionResult> UploadAsync()
{
try
{
HttpFileCollectionBase files = Request.Files;
int fileCount = files.Count;
if (fileCount > 0)
{
for (int i = 0; i < fileCount; i++)
{
CloudBlockBlob blob = blobContainer.GetBlockBlobReference(GetRandomBlobName(files[i].FileName));
blob.Properties.ContentType = files[i].ContentType;
blob.UploadFromStream(files[i].InputStream);
// the above 2 lines replace the following line from the downloaded example project:
//await blob.UploadFromFileAsync(Path.GetFullPath(files[i].FileName), FileMode.Open);
}
}
return RedirectToAction("Index");
}
}

Resources