Downloading Azure Blob files in MVC3 - asp.net-mvc

Our ASP.NET MVC 3 application is running on Azure and using Blob as file storage. I have the upload part figured out.
The View is going to have the File Name, which, when clicked will prompt the file download screen to appear.
Can anyone tell me how to go about doing this?

Two options really... the first is to just redirect the user to the blob directly (if the blobs are in a public container). That would look a bit like:
return Redirect(container.GetBlobReference(name).Uri.AbsoluteUri);
If the blob is in a private container, you could either use a Shared Access Signature and do redirection like the previous example, or you could read the blob in your controller action and push it down to the client as a download:
Response.AddHeader("Content-Disposition", "attachment; filename=" + name); // force download
container.GetBlobReference(name).DownloadToStream(Response.OutputStream);
return new EmptyResult();

Here's a resumable version (useful for large files or allowing seek in video or audio playback) of private blob access:
public class AzureBlobStream : ActionResult
{
private string filename, containerName;
public AzureBlobStream(string containerName, string filename)
{
this.containerName = containerName;
this.filename = filename;
this.contentType = contentType;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
var request = context.HttpContext.Request;
var connectionString = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;
var account = CloudStorageAccount.Parse(connectionString);
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference(containerName);
var blob = container.GetBlockBlobReference(filename);
blob.FetchAttributes();
var fileLength = blob.Properties.Length;
var fileExists = fileLength > 0;
var etag = blob.Properties.ETag;
var responseLength = fileLength;
var buffer = new byte[4096];
var startIndex = 0;
//if the "If-Match" exists and is different to etag (or is equal to any "*" with no resource) then return 412 precondition failed
if (request.Headers["If-Match"] == "*" && !fileExists ||
request.Headers["If-Match"] != null && request.Headers["If-Match"] != "*" && request.Headers["If-Match"] != etag)
{
response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
return;
}
if (!fileExists)
{
response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (request.Headers["If-None-Match"] == etag)
{
response.StatusCode = (int)HttpStatusCode.NotModified;
return;
}
if (request.Headers["Range"] != null && (request.Headers["If-Range"] == null || request.Headers["IF-Range"] == etag))
{
var match = Regex.Match(request.Headers["Range"], #"bytes=(\d*)-(\d*)");
startIndex = Util.Parse<int>(match.Groups[1].Value);
responseLength = (Util.Parse<int?>(match.Groups[2].Value) + 1 ?? fileLength) - startIndex;
response.StatusCode = (int)HttpStatusCode.PartialContent;
response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + fileLength;
}
response.Headers["Accept-Ranges"] = "bytes";
response.Headers["Content-Length"] = responseLength.ToString();
response.Cache.SetCacheability(HttpCacheability.Public); //required for etag output
response.Cache.SetETag(etag); //required for IE9 resumable downloads
response.ContentType = blob.Properties.ContentType;
blob.DownloadRangeToStream(response.OutputStream, startIndex, responseLength);
}
}
Example:
Response.AddHeader("Content-Disposition", "attachment; filename=" + filename); // force download
return new AzureBlobStream(blobContainerName, filename);

I noticed that writing to the response stream from the action method messes up the HTTP headers. Some expected headers are missing and others are not set correctly.
So instead of writing to the response stream, I get the blob content as a stream and pass it to the Controller.File() method.
CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
Stream blobStream = blob.OpenRead();
return File(blobStream, blob.Properties.ContentType, "FileName.txt");

Related

HttpContext.Current.Request.Files is randomly null when sending post request to upload file

When trying to post a file to api using following code:
public async Task UploadAcceptancePageSignaturesAsync(int scheduledWorkId, string acceptanceGuid, string operativeName, bool supervisorSignature, byte[] imageFile)
{
try
{
var content = new MultipartFormDataContent();
content.Add(new StreamContent(new MemoryStream(imageFile)), "\"file\"", "\"image.png\"");
var httpClient = new HttpClient();
var uri = _serverUrl + "api/Acceptance/UploadSignatures/" + scheduledWorkId + "/" + acceptanceGuid + "/" + operativeName + "/" + supervisorSignature;
var uploadServiceBaseAddress = uri;
var httpResponseMessage = await httpClient.PostAsync(uploadServiceBaseAddress, content);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
var x= result;
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
On server api side:
var imageFile = HttpContext.Current.Request.Files.Count > 0 ? HttpContext.Current.Request.Files[0] : null;
if (!ModelState.IsValid)
return BadRequest(ModelState);
if (imageFile == null || imageFile.ContentLength <= 0)
return BadRequest("Empty signature image");
The imageFile is most of the time null, why is that?
Thanks!

How to get oauth access token in console without authentication prompt

I want to oauth authentication like
Login using Google OAuth 2.0 with C#
But i don't want to authentication prompt popup
i want to get token directly without popup..
public ActionResult CodeLele()
{
if (Session.Contents.Count > 0)
{
if (Session["loginWith"] != null)
{
if (Session["loginWith"].ToString() == "google")
{
try
{
var url = Request.Url.Query;
if (url != "")
{
string queryString = url.ToString();
char[] delimiterChars = { '=' };
string[] words = queryString.Split(delimiterChars);
string code = words[1];
if (code != null)
{
//get the access token
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("https://accounts.google.com/o/oauth2/token");
webRequest.Method = "POST";
Parameters = "code=" + code + "&client_id=" + googleplus_client_id + "&client_secret=" + googleplus_client_sceret + "&redirect_uri=" + googleplus_redirect_url + "&grant_type=authorization_code";
byte[] byteArray = Encoding.UTF8.GetBytes(Parameters);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = byteArray.Length;
Stream postStream = webRequest.GetRequestStream();
// Add the post data to the web request
postStream.Write(byteArray, 0, byteArray.Length);
postStream.Close();
WebResponse response = webRequest.GetResponse();
postStream = response.GetResponseStream();
StreamReader reader = new StreamReader(postStream);
string responseFromServer = reader.ReadToEnd();
GooglePlusAccessToken serStatus = JsonConvert.DeserializeObject<GooglePlusAccessToken>(responseFromServer);
if (serStatus != null)
{
string accessToken = string.Empty;
accessToken = serStatus.access_token;
if (!string.IsNullOrEmpty(accessToken))
{
// This is where you want to add the code if login is successful.
// getgoogleplususerdataSer(accessToken);
}
else
{ }
}
else
{ }
}
else
{ }
}
}
catch (WebException ex)
{
try
{
var resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
dynamic obj = JsonConvert.DeserializeObject(resp);
//var messageFromServer = obj.error.message;
//return messageFromServer;
return obj.error_description;
}
catch (Exception exc)
{
throw exc;
}
}
}
}
}
return Content("done");
}
public ActionResult JeClick()
{
var Googleurl = "https://accounts.google.com/o/oauth2/auth?response_type=code&redirect_uri=" + googleplus_redirect_url + "&scope=https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&client_id=" + googleplus_client_id;
Session["loginWith"] = "google";
return Redirect(Googleurl);
}
The credentials window (popup) is how you ask the user if you can access their data. There is no way to get access to a users data without asking the user first if you may access their data. That is how Oauth2 works.
If you are accessing your own data then you can use something called a Service account. Service accounts are pre authorized. You can take the service account and grant it access to your google calendar, you could give it access to a folder in Google drive. Then you can authenticate using the service account. Service accounts are like dummy users.
My article about service accounts: Google Developer service account

Not able to properly download files from azure storage and data are lost too when downloading files

I have 2 files saved on Azure blob storage:
Abc.txt
Pqr.docx
Now i want to create zip files of this 2 files and allow user to download.
I have saved this in my database table field like this:
Document
Abc,Pqr
Now when i click on download then i am getting file like below with no data in it and file extension are lost too like below:
I want user to get exact file(.txt,.docx) in zip when user download zip file.
This is my code:
public ActionResult DownloadImagefilesAsZip()
{
string documentUrl = repossitory.GetDocumentsUrlbyId(id);//output:Abc.txt,Pqr.Docx
if (!string.IsNullOrEmpty(documentUrl))
{
string[] str = documentUrl.Split(',');
if (str.Length > 1)
{
using (ZipFile zip = new ZipFile())
{
int cnt = 0;
foreach (string t in str)
{
if (!string.IsNullOrEmpty(t))
{
Stream s = this.GetFileContent(t);
zip.AddEntry("File" + cnt, s);
}
cnt++;
}
zip.Save(outputStream);
outputStream.Position = 0;
return File(outputStream, "application/zip", "all.zip");
}
}
}
public Stream GetFileContent(string fileName)
{
CloudBlobContainer container = this.GetCloudBlobContainer();
CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
var stream = new MemoryStream();
blockBlob.DownloadToStream(stream);
return stream;
}
public CloudBlobContainer GetCloudBlobContainer()
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageConnectionString"].ToString());
CloudBlobClient blobclient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer blobcontainer = blobclient.GetContainerReference("Mystorage");
if (blobcontainer.CreateIfNotExists())
{
blobcontainer.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
}
blobcontainer.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
return blobcontainer;
}
I want same file to be downloaded when user download zip file.
Can anybody help me with this??
I'm not a web dev, but hopefully this will help. This snippet of code is in a method where I download a list of blobs into a zip file archive using a stream. The list of files had the slashes in all directions, so there's code in here to fix this, and to make sure I'm getting the blob reference with the right text (no URL, and no opening slash if the blob is in a "folder").
I suspect your problem is not using a memory stream or a binary writer. Specificity helps sometimes. Good luck.
using (ZipArchive zipFile = ZipFile.Open(outputZipFileName, ZipArchiveMode.Create))
{
foreach (string oneFile in listOfFiles)
{
//Need the filename, complete with relative path. Make it like a file name on disk, with backwards slashes.
//Also must be relative, so can't start with a slash. Remove if found.
string filenameInArchive = oneFile.Replace(#"/", #"\");
if (filenameInArchive.Substring(0, 1) == #"\")
filenameInArchive = filenameInArchive.Substring(1, filenameInArchive.Length - 1);
//blob needs slashes in opposite direction
string blobFile = oneFile.Replace(#"\", #"/");
//take first slash off of the (folder + file name) to access it directly in blob storage
if (blobFile.Substring(0, 1) == #"/")
blobFile = oneFile.Substring(1, oneFile.Length - 1);
var cloudBlockBlob = this.BlobStorageSource.GetBlobRef(blobFile);
if (!cloudBlockBlob.Exists()) //checking just in case
{
//go to the next file
//should probably trace log this
//add the file name with the fixed slashes rather than the raw, messed-up one
// so anyone looking at the list of files not found doesn't think it's because
// the slashes are different
filesNotFound.Add(blobFile);
}
else
{
//blob listing has files with forward slashes; that's what the zip file requires
//also, first character should not be a slash (removed it above)
ZipArchiveEntry newEntry = zipFile.CreateEntry(filenameInArchive, CompressionLevel.Optimal);
using (MemoryStream ms = new MemoryStream())
{
//download the blob to a memory stream
cloudBlockBlob.DownloadToStream(ms);
//write to the newEntry using a BinaryWriter and copying it 4k at a time
using (BinaryWriter entry = new BinaryWriter(newEntry.Open()))
{
//reset the memory stream's position to 0 and copy it to the zip stream in 4k chunks
//this keeps the process from taking up a ton of memory
ms.Position = 0;
byte[] buffer = new byte[4096];
bool copying = true;
while (copying)
{
int bytesRead = ms.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
entry.Write(buffer, 0, bytesRead);
}
else
{
entry.Flush();
copying = false;
}
}
}//end using for BinaryWriter
}//end using for MemoryStream
}//if file exists in blob storage
}//end foreach file
} //end of using ZipFileArchive
There are two things I noticed:
Once you read the blob contents in stream, you are not resetting that stream's position to 0. Thus all files in your zip are of zero bytes.
When calling AddEntry, you may want to specify the name of the blob there instead of "File"+cnt.
Please look at the code below. It's a console app that creates the zip file and writes it on the local file system.
static void SaveBlobsToZip()
{
string[] str = new string[] { "CodePlex.png", "DocumentDB.png" };
var account = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var blobClient = account.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("images");
using (var fs = new FileStream("D:\\output.zip", FileMode.Create))
{
fs.Position = 0;
using (var ms1 = new MemoryStream())
{
using (ZipFile zip = new ZipFile())
{
int cnt = 0;
foreach (string t in str)
{
var ms = new MemoryStream();
container.GetBlockBlobReference(t).DownloadToStream(ms);
ms.Position = 0;//This was missing from your code
zip.AddEntry(t, ms);//You may want to give the name of the blob here.
cnt++;
}
zip.Save(ms1);
}
ms1.Position = 0;
ms1.CopyTo(fs);
}
}
}
UPDATE
Here's the code in the MVC application (though I am not sure it is the best code :) but it works). I modified your code a little bit.
public ActionResult DownloadImagefilesAsZip()
{
string[] str = new string[] { "CodePlex.png", "DocumentDB.png" }; //repossitory.GetDocumentsUrlbyId(id);//output:Abc.txt,Pqr.Docx
CloudBlobContainer blobcontainer = GetCloudBlobContainer();// azureStorageUtility.GetCloudBlobContainer();
MemoryStream ms1 = new MemoryStream();
using (ZipFile zip = new ZipFile())
{
int cnt = 0;
foreach (string t in str)
{
var ms = new MemoryStream();
CloudBlockBlob blockBlob = blobcontainer.GetBlockBlobReference(t);
blockBlob.DownloadToStream(ms);
ms.Position = 0;//This was missing from your code
zip.AddEntry(t, ms);//You may want to give the name of the blob here.
cnt++;
}
zip.Save(ms1);
}
ms1.Position = 0;
return File(ms1, "application/zip", "all.zip");
}
I have seen people using ICSharpZip library, take a look at this piece of code
public void ZipFilesToResponse(HttpResponseBase response, IEnumerable<Asset> files, string zipFileName)
{
using (var zipOutputStream = new ZipOutputStream(response.OutputStream))
{
zipOutputStream.SetLevel(0); // 0 - store only to 9 - means best compression
response.BufferOutput = false;
response.AddHeader("Content-Disposition", "attachment; filename=" + zipFileName);
response.ContentType = "application/octet-stream";
foreach (var file in files)
{
var entry = new ZipEntry(file.FilenameSlug())
{
DateTime = DateTime.Now,
Size = file.Filesize
};
zipOutputStream.PutNextEntry(entry);
storageService.ReadToStream(file, zipOutputStream);
response.Flush();
if (!response.IsClientConnected)
{
break;
}
}
zipOutputStream.Finish();
zipOutputStream.Close();
}
response.End();
}
Taken from here generate a Zip file from azure blob storage files

Error in File downloading file from a folder in MVC2

I am encountering a problem in getting the download prompt. In the below code first am allowing the user to upload a file to compress. Once the file is compressed the user should be provided with the compressed files. But in the below code download prompt doesn't appears neither it shows any error. Please help me by correcting my code
The view code:
function CompressFile(box) {
var file = document.getElementById('fileComp');
if (file.value == "") {
alert("Choose a file to upload");
return false;
}
dhtmlx.modalbox.hide(box);
var fd = new FormData();
fd.append('file', file.files[0]);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/FileUpload/Compress', true);
xhr.send(fd);
}
The controller code:
public ActionResult Compress(HttpPostedFileBase file)
{
var supportedType = new[] { "pdf" };
var fileExt = System.IO.Path.GetExtension(file.FileName).Substring(1);
var filename = Path.GetFileNameWithoutExtension(file.FileName) ?? "";
if (file.ContentLength > 0 && supportedType.Contains(fileExt))
{
string filePath = Path.Combine(HttpContext.Server.MapPath(_uploadPDF), Path.GetFileName(file.FileName));
file.SaveAs(filePath);
PdfReader reader = new PdfReader(filePath);
string name = DateTime.Now.ToString("ddMM_HHmmss");
name = Server.MapPath(_fileUploadPath + name + ".pdf");
PdfStamper stamper = new PdfStamper(reader, new FileStream(name, FileMode.Create), PdfWriter.VERSION_1_5);
stamper.FormFlattening = true;
stamper.SetFullCompression();
stamper.Close();
string fn = System.IO.Path.GetFileName(name);
return base.File(name, "application/pdf",fn);
}
else
{
return View();
}
}
The problem is that you're using Ajax. You can't download a file through Ajax. You need to do a regular POST to the ActionMethod. That way the browser can send you back the file and prompt the user where he wants to save it.

Download functionality MVC 4

I have created a web api which connects users to dropbox via OAuth. I am using an API to interact with Dropbox, which works locally as I would like, however when I deploy the API to my Azure server, I am unable to download. I had anticipated this would happen, as my API is currently hard codded to a path on my machine.
Here is the method I am using:
NOTE: I call this method through an ActionResult, as part of the MVC portion of my project
public FileSystemInfo DownloadFile(string root, string path)
{
var uri = new Uri(new Uri(DropboxRestApi.ApiContentServer),
String.Format("files?root={0}&path={1}",
root, UpperCaseUrlEncode(path)));
var oauth = new OAuth();
var requestUri = oauth.SignRequest(uri, _consumerKey, _consumerSecret, _accessToken);
var request = (HttpWebRequest) WebRequest.Create(requestUri);
request.Method = WebRequestMethods.Http.Get;
var response = request.GetResponse();
var metadata = response.Headers["x-dropbox-metadata"];
var file = ParseJson<FileSystemInfo>(metadata);
using (Stream responseStream = response.GetResponseStream())
using (MemoryStream memoryStream = new MemoryStream())
{
byte[] buffer = new byte[1024];
int bytesRead;
do
{
bytesRead = responseStream.Read(buffer, 0, buffer.Length);
memoryStream.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);
file.Data = memoryStream.ToArray();
}
return file;
}
This is where I call the method in my action result.
var file = api.DownloadFile("dropbox", "Public/downloadThis.jpg");
path = file.Path;
file.Save(#"....\Desktop\DemoTest\Downloads\downloadThis.jpg"); --- this is the problem & *Save* is a stream writer
Is there a procedure to follow when downloading files from a server on a browser?
public ActionResult download(Models.downloadModel dowld, Models.LoggerView log)
{
string TC_ID = Request.QueryString["id"].ToString();
string filename = TC_ID+"_LoggerData" + ".zip";
Response.ContentType = "application/octet-stream";
Response.AppendHeader("Content-Disposition", "attachment;filename=" + filename);
Response.TransmitFile(Server.MapPath("~/files/" + filename));
Response.End();
}

Resources