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");
}
}
Related
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);
}
}
}
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);
}
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.
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);
We have a file system abstraction that allows us to easily switch between local and cloud (Azure) storage.
For reading and writing files we have the following members:
Stream OpenRead();
Stream OpenWrite();
Part of our application "bundles" documents into one file. For our local storage provider OpenWrite returns an appendable stream:
public Stream OpenWrite()
{
return new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, BufferSize, useAsync: true);
}
For Azure blob storage we do the following:
public Stream OpenWrite()
{
return blob.OpenWrite();
}
Unfortunately this overrides the blob contents each time. Is it possible to return a writable stream that can be appended to?
Based on the documentation for OpenWrite here http://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storage.blob.cloudblockblob.openwrite.aspx, The OpenWrite method will overwrite an existing blob unless explicitly prevented using the accessCondition parameter.
One thing you could do is read the blob data in a stream and return that stream to your calling application and let that application append data to that stream. For example, see the code below:
static void BlobStreamTest()
{
storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobContainer container = storageAccount.CreateCloudBlobClient().GetContainerReference("temp");
container.CreateIfNotExists();
CloudBlockBlob blob = container.GetBlockBlobReference("test.txt");
blob.UploadFromStream(new MemoryStream());//Let's just create an empty blob for the sake of demonstration.
for (int i = 0; i < 10; i++)
{
try
{
using (MemoryStream ms = new MemoryStream())
{
blob.DownloadToStream(ms);//Read blob data in a stream.
byte[] dataToWrite = Encoding.UTF8.GetBytes("This is line # " + (i + 1) + "\r\n");
ms.Write(dataToWrite, 0, dataToWrite.Length);
ms.Position = 0;
blob.UploadFromStream(ms);
}
}
catch (StorageException excep)
{
if (excep.RequestInformation.HttpStatusCode != 404)
{
throw;
}
}
}
}
There is now a CloudAppendBlob class that allows you to add content to an existing blob :
var account = CloudStorageAccount.Parse("storage account connectionstring");
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference("container name");
var blob = container.GetAppendBlobReference("blob name");
In your case you want to append from a stream:
await blob.AppendFromStreamAsync(new MemoryStream());
But you can append from text, byte array, file. Check the documentation.