ASP.NET MVC FileResult is corrupting files - c#

I've been trying to get my ASP.NET MVC website to export some data as an Excel file. For hours I thought that NPOI was just producing garbage so I switched over to EPPlus. I tested it in LINQPad and it created a proper working XLSX file, so I moved the code over to the MVC app. AGAIN, I get corrupted files. By chance I happened to look at the temp directory and saw that the file created by EPPlus is 3.87KB and works perfectly, but the FileResult is returning a file that's 6.42KB, which is corrupted. Why is this happening? I read somewhere that it was the server GZip compression causing it, so I turned it off, and it had no effect. Someone, please help me, I'm going out of my mind... Here's my code.
[HttpGet]
public FileResult Excel(
CenturyLinkOrderExcelQueryModel query) {
var file = Manager.GetExcelFile(query); // FileInfo
return File(file.FullName, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", query.FileName);
}

As far as I'm concerned there's an issue with the FileResult and it's accompanying methods. I ended up "resolving" the issue by overriding the Response object:
[HttpGet]
public void Excel(
CenturyLinkOrderExcelQueryModel query) {
var file = Manager.GetExcelFile(query);
Response.Clear();
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("Content-Disposition", "attachment; filename=" + query.FileName);
Response.BinaryWrite(System.IO.File.ReadAllBytes(file.FullName));
Response.Flush();
Response.Close();
Response.End();
}

Try using the OpenRead from FileInfo to get a file stream and see if that works.
[HttpGet]
public FileResult Excel(CenturyLinkOrderExcelQueryModel query) {
var file = Manager.GetExcelFile(query); // FileInfo
var fileStream = file.OpenRead();
return File(fileStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", query.FileName);
}

Related

Downloading file - prevent second download till first is done

I have download code functionality in my ASP.NET project and the download code look like below.
public class Download : IHttpHandler
{
private void DownloadPsListingProduct(Guid which)
{
string path = GetFilePathFromGuid(which);
FileInfo file = new FileInfo(path);
HttpResponse response = HttpContext.Current.Response;
response.ClearContent();
response.Clear();
response.ContentType = "application/octet-stream";
response.AddHeader("Content-Disposition",
"attachment;filename=\"" + file.Name.NeutralizationCrlfSequences() + "\";");
response.TransmitFile(file.FullName);
response.Flush();
response.End();
}
public bool IsReusable
{
get
{
return false;
}
}
}
This code work like a charm when I download single file at a time.
But when one file is under process of downloading and I request to download other file then it first wait to completion of first file downloading and then second file download start.
Note: I am sending new request to download each file.
I want to avoid this single file download behavior and user should able to download files without waiting previous one to complete.
ASP.NET Web API 2 should be able to handle this with very little ceremony. There's an example here, but I'll re-iterate the important parts:
public class FilesController : ApiController
{
public IHttpActionResult(Guid fileId)
{
var filePath = GetFilePathFromGuid(fileId);
var fileName = Path.GetFileName(filePath);
var mimeType = MimeMapping.GetMimeMappting(fileName);
return OkFileDownloadResult(filePath, mimeType, fileName, this);
}
}
Of course, hooking up routing etc in ASP.NET Web API 2 is quite different from hooking up an IHttpHandler, but there's also a plethora of examples on the internet (including here on SO) on how to get started with that.

Issues when downloading PDF file from MVC application

I have created a function where a user can download a pdf file from my webpage. The file is stored in a databse and is requested from a webapi. The return value of the webapi is a byte[].
My issue here is that when i run the web application on my local iis this function runs without any errors. I get the pdf file and it is downloaded correctly on my machine. But when i deploy the web application to my Test server this code generates either RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION in chrome with some of the files where as other files are downloaded to the machine but when i try to open the pdf file i get: could not load the pdf file.
This happens with both chrome and IE.
This is my code:
[HttpGet]
[DoNotChangeCacheSettings]
public virtual FileResult DownloadTranslationFile(Guid id)
{
Guid assessmentTemplateId = id;
File translationFile = Services.GetFileContent(assessmentTemplateId);
var fileName = HttpUtility.UrlPathEncode(translationFile.FileName);
this.HttpContext.Response.Headers.Add("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
var result = File(translationFile.FileContent.Content, System.Net.Mime.MediaTypeNames.Application.Pdf, fileName);
return result;
}
I have been trying to fix this issue for 2 days now but i simply cant figure out what the issue is. Hope you guys can help. Thanks.
You don't need to use Content-Disposition. .Net will add it for you. From the docs.
The fileDownloadName parameter is used to generate the
content-disposition header. The result object that is prepared by this
method is written to the response by the ASP.NET MVC framework when
the object is executed. The MediaTypeNames class can be used to get
the MIME type for a specific file name extension.
I tend to use the Stream-overload:
[HttpGet]
[DoNotChangeCacheSettings]
public virtual FileResult DownloadTranslationFile(Guid id)
{
Guid assessmentTemplateId = id;
File translationFile = Services.GetFileContent(assessmentTemplateId);
var fileName = HttpUtility.UrlPathEncode(translationFile.FileName);
var stream = = new MemoryStream(translationFile.FileContent.Content);
return File(stream, "application/pdf", fileName);
}
But you can use the byte[] as well:
[HttpGet]
[DoNotChangeCacheSettings]
public virtual FileResult DownloadTranslationFile(Guid id)
{
Guid assessmentTemplateId = id;
File translationFile = Services.GetFileContent(assessmentTemplateId);
var fileName = HttpUtility.UrlPathEncode(translationFile.FileName);
return File(translationFile.FileContent.Content, "application/pdf", fileName);
}
EDIT:
If you got an error when opening the PDF you can ensure that the web browser is doing the right thing by manually saving the PDF from code as well. If that file has errors as well you're probably generating an incorrect byte[].
[HttpGet]
[DoNotChangeCacheSettings]
public virtual FileResult DownloadTranslationFile(Guid id)
{
Guid assessmentTemplateId = id;
File translationFile = Services.GetFileContent(assessmentTemplateId);
var fileName = HttpUtility.UrlPathEncode(translationFile.FileName);
var stream = = new MemoryStream(translationFile.FileContent.Content);
// Code for debugging
var tempDir = "C:\\temp"; // Make sure app pool can write here.
var path = Path.Combine(tempDir, fileName); // Possibly add extension here.
using (var fileStream = File.Create(path))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(fileStream);
}
stream.Seek(0, SeekOrigin.Begin);
// Return to client.
return File(stream, "application/pdf", fileName);
}

Corrupt File when streaming from HTTP

I’m having an issue with streaming a file through the HTTP Response. No matter what I do, it comes out corrupted!
The background is that I need to send a generated XLS file (I'm using NPOI). I know that the generated file is fine, because if I save it directly to the disk with a FileStream, I can open it and there are no problems! However… when I try to stream that file through HTTP, it comes out corrupted (I’ve tried three different methods, shown below…). To add on top of that, it’s not only the XLS file that gets corrupted, it’s ALL files that I load (I’ve tried jpg, png, and txt files). Whenever I send them through HTTP, it gets corrupted.Anyways, here’s what I’ve tried:
I’ve tried manually constructing an HTTP response:
Export export = new Export(header, data);
MemoryStream stream = export.GetXLSStream("test"); // This generates a memory stream of the XLS file
// Writing that stream to a file works! This file opens just fine
var fs = new FileStream(#"C:\export.xls", FileMode.Create, System.IO.FileAccess.Write);
stream.WriteTo(fs);
// However, this doesn't!
Response.ClearContent();
Response.AddHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
Response.AddHeader("Content-Disposition", "attachment; filename=export.xls");
Response.AddHeader("Content-Type", "application/vnd.ms-excel");
Response.AddHeader("Content-Transfer-Encoding", "binary");
Response.BinaryWrite(stream.ToArray());
Response.End();
return null;
I’ve tried using the FileStreamResult:
Export export = new Export(header, data);
MemoryStream stream = export.GetXLSStream("test"); // This generates a memory stream of the XLS file
return File(stream, "application/vnd.ms-excel", "export.xsl");
I’ve tried using the FileContentResult:
Export export = new Export(header, data);
MemoryStream stream = export.GetXLSStream("test"); // This generates a memory stream of the XLS file
return File(stream.ToArray(), "application/vnd.ms-excel", "export.xsl");
I’ve tried using a FilePathResult:
Export export = new Export(header, data);
MemoryStream stream = export.GetXLSStream("test"); // This generates a memory stream of the XLS file
var fs = new FileStream(#"C:\export.xls", FileMode.Create, System.IO.FileAccess.Write);
stream.WriteTo(fs);
fs.Close();
return File(#"C:\export.xls", "application/vnd.ms-excel", "export.xsl");
And I’ve tried loading random files like:
return File(#"C:\test.jpg", "image/jpeg", "test.jpg");
Doing a MD5 or CRC check also shows me that the file I get through HTTP is not the same as the original file (even though they have the exact same amount of bytes).
Here is the code that works for me:
public MyController : Controller {
public ActionResult GetFile()
{
// ...
return File(stream, "application/octet-stream", "file.xls");
}
}
I do it in MVC with NPOI 1.2.5, .NET 4.0 in following way:
I have custom ActionResult class with following method:
public override sealed void ExecuteResult(ControllerContext context)
{
IWorkbook workbook = XlsData.CreateTestWorkbook().Workbook;
HttpResponseBase response = context.HttpContext.Response;
response.Clear();
response.ContentType = "application/vnd.ms-excel";
response.Headers.Add("content-disposition", "attachment; filename=Test.xls");
using (var ms = new MemoryStream())
{
workbook.Write(ms);
ms.WriteTo(response.OutputStream);
}
response.End();
}
where XlsData.CreateTestWorkbook() is some class which creates me NPOI sample workbook.
Then in my Controller method I simply return my custom ActionResult
My controller method has a [HttpGet] attribute and is called from
client side with a html link download button.
Looks like it was a Whitespace filter I had applied at the controller's root to make the output all a single line. It shouldn't have been applied to anything but an html response, but I've changed the code so that it doesn't run at all for response like this.
Try this method, it works fine for me
using (var fileData = new FileStream(filename, FileMode.Create))
{
workbook.Write(fileData);
}
using (var exportData = new MemoryStream())
{
workbook.Write(exportData);
string saveAsFileName = string.Format("MembershipExport-{0:d}.xls", DateTime.Now).Replace("/", "-");
Response.ContentType = "application/vnd.ms-excel";
Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", saveAsFileName));
Response.Clear();
Response.BinaryWrite(exportData.GetBuffer());
Response.End();
}
MemoryStream outStream = (MemoryStream)generateFilte.Generate();
outStream.Flush(); //Always catches me out
outStream.Position = 0;
return new FileStreamResult(outStream, "application/vnd.ms-excel");

How to show download dialog before file is ready to download?

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.

MVC C# Download file and save as dialog

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");
}

Categories