I'm getting a file from a database in byte [] format and want user to see download dialog before Linq will take it from the database. It's in C# and ASP.NET.
Now, it's like this:
User choose a file, click on it.
In code I get id of file clicked and using Linq I'm downloading.
Then I send the file by Response.OutputStream.Write(content, 0,
content.Length);
Before a file is downloaded from the database user won't see any
download dialog.
What can I do if I want users to see the download dialog before file is downloaded?
Code:
Getting file by id:
public static byte[] getFile(Guid id)
{
var linqFile = from file in MyDB.Files
where file.IdPliku.Equals(id)
select new
{
Content = file.Content
};
return linqFile.ToList().FirstOrDefault().Content.ToArray();
}
Saving file:
public void SaveFile(Guid fileID, string filename, string mimeTypes)
{
try
{
byte[] content = FileService.getFile(fileID);
Response.ClearContent();
Response.ClearHeaders();
Response.ContentType = mimeTypes;
Response.AppendHeader("Accept-Ranges", "bytes");
Response.AppendHeader("Content-Range", string.Format("0-{0}/{1}", content.Length, content.Length));
Response.AppendHeader("Content-Length", content.Length.ToString());
Response.AppendHeader("Content-Encoding", "utf-8");
Response.AppendHeader("Content-Type", Response.ContentType);
Response.AppendHeader("Content-Disposition", "attachment; filename= " + HttpUtility.UrlEncode(filename));
Response.OutputStream.Write(content, 0, content.Length);
//Response.BinaryWrite(content);
Response.Flush();
}
finally
{
Response.Close();
}
}
You are my hope.
your issue is here:
byte[] content = FileService.getFile(fileID);
because in this line you allocate the whole file in the web server's RAM and put everything in there, all content of the file from the database; what happens later does not matter anymore because you have already downloaded from db to web server in this line!!!
I am having such Deja-vu because I am sure I have given exactly the same comment on a very same question few weeks ago. Can't find it now, search for something like this here in SO.
In fact the solution is to stream directly to the output stream of the Response avoiding your byte[] array allocation above, to get this your data layer should of course support it and if it does not you could add a method for this. You want to use SQL Server filestream or something similar.
Related
I am generating some JSON content, and then GZipping that content, before returning the gzipped content to the user, from an MVC Controller Action.
The generation of the content, and gzipping, is working correctly as I can output the generated file to disk, and then I can open that file using GZip. However, when the content is returned to the browser, the content has been corrupted.
I have tried several different approaches to returning the content to the browser, such as
return File(byte[], "application/gzip");
return new FileStreamResult(stream, "application/gzip")
And also writing directly to the Response using BinaryWrite() and WriteFile() methods
No matter what I do, the file I receive in the browser is corrupt.
This code shows the manner in which I am currently trying to return the file content.
// This line writes my content byte[] array to disk. This file when opened with gzip works fine.
System.IO.File.WriteAllBytes(#"C:\temp\test.vcp", result.FileBytes);
// Writing out the byte array to the Response results in a corrupt file. I have also attempted to Response.WriteFile(#"C:\temp\test.vcp") which also results in a corrupt file.
Response.Clear();
Response.ContentType = "application/gzip";
Response.AppendHeader("Content-Disposition", cd.ToString());
Response.AddHeader("Content-Length", result.FileBytes.Length.ToString());
Response.BinaryWrite(result.FileBytes);
Response.Flush();
Response.Close();
Response.End();
As the file I am creating can be written to disk, and can be read using Gzip, but the file received by the browser is corrupt, I am confident that my file creation is OK. But somehow after writing the file to the Response, it is being corrupted.
I did wonder if maybe some sort of HTTPHandler is manipulating the result, but I haven't added any Handlers (that I can see).
I am running the application locally currently through IISExpress. How can I check what HttpHandlers/HttpModules are being applied to the pipeline?
Ultimately I expect to receive the exact same file in my browser as is written to disk.
For reference, my generated content is 132 bytes in length, but the browser receives 216 bytes. I have noticed when looking at the byte structure of the received data, there is a repeating pattern of 3 bytes in the content, with the values 239, 191, 189. It almost looks like the resultant byte array has been stuffed or padded with these 3 bytes.
EDIT
Here is a standalone Action method which demonstrated the issue.
[HttpGet]
public void GetFile()
{
byte[] text = Encoding.ASCII.GetBytes(#"{""PetName"":""Doggy McDocFace"",""OwnerName"":""Kurt""}");
byte[] compressed = Compress(text);
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = "ExampleFile.vcp",
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = true,
};
System.IO.File.WriteAllBytes(#"C:\temp\ExampleFile.vcp", compressed);
Response.Clear();
Response.ContentType = "application/gzip";
Response.AppendHeader("Content-Disposition", cd.ToString());
Response.AddHeader("Content-Length", compressed.Length.ToString());
Response.BinaryWrite(compressed);
Response.Flush();
Response.Close();
Response.End();
}
public byte[] Compress(byte[] raw)
{
using (var memory = new MemoryStream())
{
using (var gzip = new GZipStream(memory, CompressionMode.Compress, true))
{
gzip.Write(raw, 0, raw.Length);
}
return memory.ToArray();
}
}
Here I am spoofing my JSON content, and then compressing it. The file written to disk works fine, and can be opened with my GZip application (I use 7-zip). However, the file received by the browser is corrupt. 7-zip cannot recognise it as a gzip file.
EDIT 2
So it looks like (Thanks to #Will) that the content when written to Response is falling foul of UTF-8 encoding. I cannot work out how though, as in my example above I am using Encoding.ASCII.GetBytes() to convert my string to a byte[] array.
I've tried setting the
Response.Charset = Encoding.ASCII.EncodingName;
Response.ContentEncoding = Encoding.ASCII;
But this still doesn't result in a valid file downloaded.
Edit 3
I've narrowed down the issue to the GZip encryption of the data. If I do not encrypt the data, then the plain text file downloads fine. However, encrypting the byte[] array and then writing that byte[] array to the Repsonse is resulting in what seems like UTF-8 encoding issues. Any bytes with a value over 127 are corrupted with the 3 bytes I mention further up. I cannot work out why the Response is treating this encrypted data in this way. My assumption is that when the Byte[] array is just plain text as a byte[] array, then this is handled fine. As soon as it is a proper byte[] array, i.e not just a string as a byte[] array, then some other conversion of encoding is going on in the Response.
You can try ActionFilterAttribute
Basically response filters look at the Response output stream as it's written and convert the data flowing through it.
GZip/Deflate Compression in ASP.NET MVC
public class CompressAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var encodingsAccepted = filterContext.HttpContext.Request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(encodingsAccepted)) return;
encodingsAccepted = encodingsAccepted.ToLowerInvariant();
var response = filterContext.HttpContext.Response;
if (encodingsAccepted.Contains("deflate"))
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
else if (encodingsAccepted.Contains("gzip"))
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
}
}
[Compress]
[HttpGet]
public ActionResult GetFile()
{...}
I have list of PDF files on website (asp.net webforms). i want to open them with Save As option rather than it downlaods directly.
I tried to add download property to the link which didn't work. only was around seems to be HTTPHandler for *.pdf request.
I saw a piece of code for MVC based example here
return new FileStreamResult(stream, "application/pdf")
{
FileDownloadName = "file.pdf"
};
How can i convert this to HTTPHandler in as.net webform so that it open pdf files with Save As option.
I want to do it in a way so that when ever user click on any pdf file at that time Handler should come into action.
OR
I can create another file handlePDF.aspx and write code there also and will change link of pdf file to below
File One
If what you are trying to do is when they click on the file download link it pops up with save as or open dialog box, this is to do with the user's browser configuration. In the case of PDF's i believe Firefox has open in tab as the default option. If you try to push the file as a file stream it will more than likely just load it in a new tab as well.
tl;dr: Client side issue
You're on the right track. Serving PDF files are usually handled by an HttpHandler. That is, unless they can be served straight from the file system by the StaticHandler...
The key thing that is needed in order for the browser to raise the "Open or save" dialog is the Content-Disposition header in the response.
Here is an (untested) implementation that should get you on the right track:
public void ProcessRequest(HttpContext context)
{
string fileName = context.Request.QueryString["file"];
if(fileName == null || fileName == "")
{
throw new ArgumentException("The file argument cannot be null or empty");
}
// fetch file here from db/filesystem/other storage
byte[] fileBytes = LoadFileBytes(fileName);
context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
context.Response.ContentType = "application/pdf";
context.Response.BinaryWrite(fileBytes);
}
If you want to avoid buffering the whole file in memory, this might also work (requires .Net 4.0 for the CopyTo method on the stream):
public void ProcessRequest(HttpContext context)
{
string fileName = context.Request.QueryString["file"];
if(fileName == null || fileName == "")
{
throw new ArgumentException("The file argument cannot be null or empty");
}
// fetch file stream from db/filesystem/other storage
Stream fileStream = GetFileStream(fileName);
context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
context.Response.ContentType = "application/pdf";
fileStream.CopyTo(context.Response.OutputStream);
}
I have the code below which works well for small files but for large files it generates the zip as required but doesn't download it. I get all sorts of errors including Timeout (which I have managed to resolve). The other problem is that it runs in Sync. The largest file I have generated myself is a 330MB zip file with about 30 HD images attached to it. But this can even go to GBs as the user can choose to download about 100 or even more HD images at once.
To resolve both issues, I thought downloading in async may help in both cases. I want to alert the user that their download has started, and that they will be notified when it is ready.
I am thinking of sending the stream down if the client IsConnected (then delete the file) or sending an email to ask them to download the file if they have decided to logout (then delete the file using the offline download link). I just don't know where or how to write async code, or if what I want to do can actually be done if the user decides to logout.
Here's my current code:
private void DownloadFile(string filePath)
{
FileInfo myfile = new FileInfo(filePath);
// Checking if file exists
if (myfile.Exists)
{
// Clear the content of the response
Response.ClearContent();
// Add the file name and attachment, which will force the open/cancel/save dialog box to show, to the header
Response.AddHeader("Content-Disposition", "attachment; filename=" + myfile.Name);
// Add the file size into the response header
Response.AddHeader("Content-Length", myfile.Length.ToString());
// Set the ContentType
Response.ContentType = "application/octet-stream";
Response.TransmitFile(filePath);
Response.Flush();
try
{
myfile.Delete();
}
catch { }
}
}
I don't know about Async downloads from asp.net applications so I can't address that question. But I have run into enough download issues to always start from the same place.
First, download from a generic handle (ASHX) and not a web form. The webform wants to do extra processing at the end of the request that can cause problems. You question didn't state if you are using a web form or generic handler.
Second, always end the request with the ApplicationInstance.CompleteRequest() method call. Don't use Request.Close() or Request.End()
Those two changes have often cleaned up download issues for me. Try these change and see if you get the same results. Even if you do get the same results this is a better way of coding downloads.
Finally, as an aside, only catch appropriate exceptions in the try-catch bock.
Your code would be like this:
public class Handler1 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
// set from QueryString
string filePath = "...";
FileInfo myfile = new FileInfo(filePath);
// Checking if file exists
if (myfile.Exists)
{
// Clear the content of the response
context.Response.ClearContent();
// Add the file name and attachment, which will force the open/cancel/save dialog box to show, to the header
context.Response.AddHeader("Content-Disposition", "attachment; filename=" + myfile.Name);
// Add the file size into the response header
context.Response.AddHeader("Content-Length", myfile.Length.ToString());
// Set the ContentType
context.Response.ContentType = "application/octet-stream";
context.Response.TransmitFile(filePath);
context.Response.Flush();
HttpContext.Current.ApplicationInstance.CompleteRequest();
try
{
myfile.Delete();
}
catch (IOException)
{ }
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
Hi all wondering if someone can help; i've written this code which will generate an excel spreadsheet and save it to a specified location. I want to then display a "Save as" dialogue box by reading the file from the stored location and then asking then user where they want to store it. The excel file is being generated fine and i can open it normally! However my problem is the code i've written seems to be outputting the file directly to my browser, so i get all the contents of the excel file on my browser screen, not displaying the save as dialogue box as expected!
public ActionResult FormSuccess()
{
String FileName = System.Configuration.ConfigurationManager.AppSettings["FileName"].ToString();
String FilePath = System.Configuration.ConfigurationManager.AppSettings["FileSaveLocation"].ToString();
System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;
response.ClearContent();
response.Clear();
response.ContentType = "application/vnd.xls";
response.AddHeader("Content-Disposition", "attachment; filename=" + FileName + ";");
response.TransmitFile(FilePath + FileName);
response.End();
return PartialView("FormSuccess");
}
Yo Vince, how's tricks? Still wearing the medallion? :)
Shouldn't you be using FileContentResult instead of PartialView? You won't be able to return the file AND the HTML "success" content in the same call - you should probably call the PartialView first, which would then use javascript to open the FileContentResult URL in a new window.
See this: http://www.mikesdotnetting.com/Article/125/ASP.NET-MVC-Uploading-and-Downloading-Files
and this url as well :
http://weblogs.asp.net/rajbk/archive/2010/05/03/actionresult-types-in-mvc2.aspx
I think that your problem is that you return PartialView. Let me give you small exmaple of my implemetation:
public ActionResult FileXls()
{
var output = new MemoryStream();
var writer = new StreamWriter(output);
//create your workbook excel file
....
//workbook.Save(output);
writer.Flush();
output.Position = 0;
return File(output, "text/excel", "file.xls");
}
If I have a URL to a download, www.website.com/myfile.html
so when that link is clicked it automatically starts a download, which may be myfile.txt for example, how would I get that file into C# for reading..
Is that what Net.WebRequest.Create(url), Net.HttpWebRequest does?
You could achieve this using WebClient:
using (var client = new WebClient())
{
// Download and save the file locally
client.DownloadFile("http://www.website.com/myfile.html", "myfile.html");
}
If you don't want to store the file locally but only read the contents you could try this:
using (var client = new WebClient())
{
string result = client.DownloadString("http://www.website.com/myfile.html");
}
Using C# as an example, here is how one might force the download of a file after clicking a button, link, etc...
public void DownloadFileLink_Click(object sender, EventArgs e)
{
//Get the file data
byte[] fileBytes = Artifacts.Provider.GetArtifact(ArtifactInfo.Id);
//Configure the response header and submit the file data to the response stream.
HttpContext.Current.Response.AddHeader("Content-disposition", "attachment;filename=" + "myDynamicFileName.txt");
HttpContext.Current.Response.ContentType = "application/octet-stream";
HttpContext.Current.Response.BinaryWrite(fileBytes);
HttpContext.Current.Response.End();
}
With this in mind, what you need to look for is the Header in the response, the Header will contain an item Content-disposition which will contain the filename of the file being streamed in the response.