Calling Response.TransmitFile() from static method - c#

I have a number of pages which need to support exporting data to an Excel spreadsheet. I can generate the Excel files just fine, but I'm trying to work out how to abstract this behavior so it's easily reusable from all of the pages where I need it. My current idea is to use a static utility method, as follows:
public static void SendExcelFile(System.Web.UI.Page callingPage, string downloadFileName, List<List<string>> data, string worksheetTitle)
{
string tempFileName = Path.GetTempFileName();
try
{
// Generate file using ExcelPackage
GenerateExcelDoc(tempFileName, data, worksheetTitle);
callingPage.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
callingPage.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
callingPage.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString());
callingPage.Response.TransmitFile(tempFileName);
}
finally
{
//When this is removed, the method works as expected.
if (File.Exists(tempFileName))
File.Delete(tempFileName);
}
}
The click handler where I'm calling SendExcelFile looks like this:
protected void lnkExport_Click(object sender, EventArgs e)
{
List<List<string>> dataList = GatherDataForSpreadsheet();
Utility.SendExcelFile(this, "fileNameForDownload.xlsx", dataList, "MyReports");
}
This code works just fine as an instance method of the calling page. As a static method, though, it doesn't work at all. When I click the button that invokes this, the browser shows the loading animations indefinitely, but never prompts for a file download.
I'm very new to ASP.NET (and web programming in general), so I'm sure I'm missing something here. Could someone please explain the behavior I'm seeing, and suggest a reasonable alternative to this approach?
EDIT: If I remove the call to File.Delete() at the end, the method works as expected. Does Response.TransmitFile() do the transfer asynchronously?
EDIT 2: I just needed to call Response.Flush() before I deleted the file. See my answer below.
Thanks!

The problem was that the temp file was being deleted before the data was sent down. I just needed to call Response.Flush() like so:
public static void SendExcelFile(System.Web.UI.Page callingPage, string downloadFileName, List<List<string>> data, string worksheetTitle)
{
string tempFileName = Path.GetTempFileName();
try
{
// Generate file using ExcelPackage
GenerateExcelDoc(tempFileName, data, worksheetTitle);
callingPage.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
callingPage.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
callingPage.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString());
callingPage.Response.TransmitFile(tempFileName);
callingPage.Response.Flush(); //This is what I needed
}
finally
{
if (File.Exists(tempFileName))
File.Delete(tempFileName);
}
}

Try this, you can get the Request and Response directly off HttpContext.Current:
public static void SendExcelFile(string downloadFileName, List<List<string>> data, string worksheetTitle)
{
var context = HttpContext.Current;
string tempFolder = context.Request.PhysicalApplicationPath + "temp";
string tempFileName = tempFolder + "tempFileName.xlsx"
if (!Directory.Exists(tempFolder))
Directory.CreateDirectory(tempFolder);
// Generate file using ExcelPackage
GenerateExcelDoc(tempFileName, data, worksheetTitle);
context.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
context.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString());
context.Response.TransmitFile(tempFileName);
File.Delete(tempFileName);
}
Another alternative is a base class for your pages that contains this method, that may be a much easier route. You pages don't have to inherit from System.Web.UI.Page, they can inherit from something else, like this:
public class BasePage : System.Web.UI.Page
{
public void SendExcelFile(string downloadFileName, List<List<string>> data, string worksheetTitle)
{
string tempFolder =Request.PhysicalApplicationPath + "temp";
string tempFileName = tempFolder + "tempFileName.xlsx"
if (!Directory.Exists(tempFolder))
Directory.CreateDirectory(tempFolder);
// Generate file using ExcelPackage
GenerateExcelDoc(tempFileName, data, worksheetTitle);
Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString());
Response.TransmitFile(tempFileName);
File.Delete(tempFileName);
}
}
Then in your page the class looks like:
public partial class MyPage : BasePage
{
//Stuff!
}

We need more information - what you're doing should work.
I created a stripped-down version that just sends a copy of the calling page to the client and it works as expected:
public class Utility {
// This just sends the client a copy of the calling page itself
public static void SendExcelFile(Page callingPage) {
string path = callingPage.Request.PhysicalPath;
callingPage.Response.AddHeader("Content-Disposition", "attachment;filename=test.xls");
callingPage.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
callingPage.Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());
callingPage.Response.TransmitFile(path);
}
}
Here's my calling page:
public partial class main : System.Web.UI.Page {
protected void SendButton_Click(object sender, EventArgs e) {
Utility.SendExcelFile(this);
}
}
Do you see any differences from your implementation?

At this point I'd use an HTTP debugging proxy like Fiddler to compare the HTTP sessions generated by both the working (page codebehind) and nonworking (static) versions of your code.
As an aside, you should be aware that your code as written won't work well if more than one user clicks the button at the same time -- the first user's temp file may get overwritten by the second user's file, and the second user's file may get deleted in the middle of being transmitted! Consider using Path.GetTempFileName() or a guid in the filename to ensure that each user's file is uniquely named.

I would use this instead. The current HTTP context will be available on every page.
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
HttpContext.Current.Response.AddHeader("Content-Length", new FileInfo(tempFileName).Length.ToString());
HttpContext.Current.Response.TransmitFile(tempFileName);

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.

Download a Large file Async in ASP.NET C#

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

Trouble cleaning up after serving generated file from asp.net Page_Load

I want to serve a dynamically created binary file from an asp.net web page via a http request parameter like so: www.host.com/?Download=file. So far I have
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params.Allkeys.Contains("Download"))
{
String fileStr = Request.Params.GetValues("Download")[0];
using (Stream generatedFile = File.Create(#"C:\Temp\file"))
{
/* write the contents of the file */
}
Response.ContentType = "application";
Response.AddHeader("Content-Disposition",
"attachment; filename=" + fileStr);
Response.WriteFile(#"C:\Temp\file");
Response.End();
}
}
which works, but C:\Temp\file still exists and I'd like it cleaned up. Calling File.Delete(#"C:\Temp\file"); after Response.End() doesn't work, since Response.End() kills the thread. Putting the Response methods inside the using block, with File.Create( (...), FileOptions.DeleteOnClose) doesn't work, since the Response methods then fail to get access to the file.
Any suggestions?
After writing file to response you can call Response.Flush to make sure it was fully written, and then delete it before calling Response.End:
Response.WriteFile(#"C:\Temp\file");
Response.Flush();
File.Delete(#"C:\Temp\file");
Response.End();

How can I open a pdf file directly in my browser?

I would like to view a PDF file directly in my browser. I know this question is already asked but I haven't found a solution that works for me.
Here is my action's controller code so far:
public ActionResult GetPdf(string fileName)
{
string filePath = "~/Content/files/" + fileName;
return File(filePath, "application/pdf", fileName);
}
Here is my view:
#{
doc = "Mode_d'emploi.pdf";
}
<p>#Html.ActionLink(UserResource.DocumentationLink, "GetPdf", "General", new { fileName = doc }, null)</p>
When I mouse hover the link here is the link:
The problem with my code is that the pdf file is not viewed in the browser but I get a message asking me if I wand to open or save the file.
I know it is possible and my browser support it because I already test it with another website allowing me to view pdf directly in my browser.
For example, here is the link when I mouse hover a link (on another website):
As you can see there is a difference in the generated link. I don't know if this is useful.
Any idea how can I view my pdf directly in the browser?
The reason you're getting a message asking you to open or save the file is that you're specifying a filename. If you don't specify the filename the PDF file will be opened in your browser.
So, all you need to do is to change your action to this:
public ActionResult GetPdf(string fileName)
{
string filePath = "~/Content/files/" + fileName;
return File(filePath, "application/pdf");
}
Or, if you need to specify a filename you'll have to do it this way:
public ActionResult GetPdf(string fileName)
{
string filePath = "~/Content/files/" + fileName;
Response.AddHeader("Content-Disposition", "inline; filename=" + fileName);
return File(filePath, "application/pdf");
}
Instead of returning a File, try returning a FileStreamResult
public ActionResult GetPdf(string fileName)
{
var fileStream = new FileStream("~/Content/files/" + fileName,
FileMode.Open,
FileAccess.Read
);
var fsResult = new FileStreamResult(fileStream, "application/pdf");
return fsResult;
}
Change your code to this :
Response.AppendHeader("Content-Disposition","inline;filename=xxxx.pdf");
return File(filePath, "application/pdf");
If you read the file stored in database image column, you can use like this:
public ActionResult DownloadFile(int id)
{
using (var db = new DbContext())
{
var data =
db.Documents.FirstOrDefault(m => m.ID == id);
if (data == null) return HttpNotFound();
Response.AppendHeader("content-disposition", "inline; filename=filename.pdf");
return new FileStreamResult(new MemoryStream(data.Fisier.ToArray()), "application/pdf");
}
}
If you are using Rotativa package to generate PDF, Then don't put a name to file with FileName attribute like below example.
return new PartialViewAsPdf("_JcPdfGenerator", pdfModel);
Hope this is helpful to someone.
Although previous posts are often correct; I think most of them are not best practice!
I'd like to suggest to change action return types to FileContentResult and usereturn new FileContentResult(fileContent, "application/pdf"); at the end of action body.
Yes You Can do It Simply by redirecting . it ends extension like u need , .pdf ..
protected void OpenPdfPdf_Click(object sender, EventArgs e)
{
Response.Redirect("jun.pdf");
}
Or another Method ,its opens like .aspx page--
protected void OpenPdf_Click(object sender, EventArgs e)
{
string path = Server.MapPath("jun.pdf");
//or you want to load from url change path to
//string path="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
WebClient client = new WebClient();
Byte[] buffer = client.DownloadData(path);
if (buffer != null)
{
Response.ContentType = "application/pdf";
Response.AddHeader("content-length", buffer.Length.ToString());
Response.BinaryWrite(buffer);
}
}

downloading multiple files using Ihttphandler

Greatings!
I'm working on a reporting script which runs a number of reports (pdf) on button click. The reports are created on the web server then I'd like the user to be given the option to download the files. I have worked out the script for downloading one file from the server. But I'm not sure how to download multiple files? (there will probably be about 50)
After I run one report I redirect the user to a http handler script.
Response.Redirect("Download.ashx?ReportName=" + "WeeklySummary.pdf");
public class Download : IHttpHandler {
public void ProcessRequest(HttpContext context)
{
StringBuilder sbSavePath = new StringBuilder();
sbSavePath.Append(DateTime.Now.Day);
sbSavePath.Append("-");
sbSavePath.Append(DateTime.Now.Month);
sbSavePath.Append("-");
sbSavePath.Append(DateTime.Now.Year);
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.ContentType = "application/pdf";
HttpResponse objResponce = context.Response;
String test = HttpContext.Current.Request.QueryString["ReportName"];
HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=" + test);
objResponce.WriteFile(context.Server.MapPath(#"Reports\" + sbSavePath + #"\" + test));
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.End();
}
public bool IsReusable { get { return false; } }
}
Thanks in advance, please let me know if you'd like to see any more of my script.
The 2 options I see right away is the obvious one to simply call the HTTP Handler repeatedly. Another one would be to zip them on the server and send a zip file across the wire. You could use the built in GZipStream class to accomplish this.
Also, you'll want to add some code in your handler to clean up those temp files once they're downloaded.

Categories