How to identify whether a download is completed using an ashx handler - c#

In one of our project we need the functionality to download a file from server to client location.
For this we are using an ashx handler to do the operation. Its working perfectly and we are able to download files.
Now we need a requirement like we need to update a field when a download is started and completed. Is there any way to do this.
Once we click the download link the Save as dialog box will appear and after that i think we don't have any control to check the progress. I think we even don't know which button is clicked ie we don't know whether the user is clicked a 'Yes' or 'No'.
Can anyone please suggest a method to know when the download is started and when it has been completed? We are using Asp.Net 2.0 with c#.
The handler used for download is given below
string fileUrl = string.Empty;
if (context.Request["fileUrl"] != null)
{
fileUrl = context.Request["fileUrl"].ToString();
}
string filename = System.IO.Path.GetFileName(fileUrl);
context.Response.ClearContent();
context.Response.ContentType = "application/exe";
context.Response.AddHeader("content-disposition", String.Format("attachment; filename={0}", filename));
context.Response.TransmitFile(fileUrl);
context.Response.Flush();
The file is downloaded from an aspx page method like
private void DownloadExe()
{
string downloadUrl = "Test.exe");
Response.Redirect("Test.ashx?fileUrl=" + downloadUrl, false);
}

Your ASHX handler knwos if download started (since it is actually get called) and when download is completed (end of handler is reached). You may even get some progress server side if you are writing response manually in chunks, this way you also may be able to detect some cases when user cancels download (if writing to response stream fails at some point).
Depending on your needs you may be able to transfer this information to other pages (i.e. via session state) or simply store in some database.

How about this:
Response.BufferOutput = false;
Response.TransmitFile(fileUrl);
//download complete code
If you disable response output buffering then it won't move past the line of code that sends the file to the client until the client has finished receiving it. If they cancel the download half way through it throws a HttpException so the download complete code doesn't get run.
You could also place your download complete code after your call to flush the buffer. But it's better not to enable buffering when sending large binary files to save on server memory.

Ok I had the same problem and jumped over this site:
Check over coockies
This works great for me.

Related

Making Second HTTP Request (before first ends) closes connection to the first

I have a website that allows users to download files via clicking href links that will call an HTTP GET request and return the file (will prompt user in IE and auto-download in chrome)
public async Task<IActionResult> DownloadFile(DocumentFile file){
fileName = DocumentTask.RetrieveFiles(new string[1] { file.FileID }, file.Facility, UserData.Access).SingleOrDefault();
if (System.IO.File.Exists(fileName))
{
FileInfo info = new FileInfo(fileName);
return File(System.IO.File.ReadAllBytes(fileName), FileUtility.GetContentType(fileName), info.Name);
}
}
When a user clicks a download link:
<i class="file pdf outline icon"></i>TestFile.pdf
and than immediately after clicks another download link (before the first download link has returned a response) it appears the client closes the connection to the first request. It will give this error:
The remote host closed the connection. The error code is 0x800703E3.
This error gets thrown when the server attempts to return the file back to the user, but it can't because the connection has been closed.
Using the Chrome developer tools, I can see both requests getting queued however as soon as the second request is sent, it cancels the first request (closing the connection).
Chrome Developer Request Cancelled
Right now I have disabled the user from clicking another download link until the previous request has returned however I would like to know if there is a more elegant solution to allow for multiple requests to be sent and waited for?
(I have tested this in Chrome and IE 11 and both cancel the previous request sent)
Thanks in advance.
Maybe you could use a function with the form of the next one to reach your objetive, I've tested it and it doesn't stop receving files, let me know if this works for you:
public Stream Downloadfile(string filename)
{
WebOperationContext.Current.OutgoingResponse.Headers["Content-Disposition"] = "attachment; filename=" + filename;
WebOperationContext.Current.OutgoingResponse.ContentType = "application/octet-stream";
return File.OpenRead(filename);
}
I was unable to find an answer to my solution. However I was able to change the way my links worked. By changing the links target="_blank" it will open up a new page and close when the file has begun downloading. This stops the user from selecting a second link until the other returns, but also if they use the middle mouse button to open the link but not change tab focus, this stops the error from occurring. I think multiple requests from the same tab was the issue.

Streaming MP3 Chunks on ASP.NET

Currently, I have a feature on an ASP.NET website where the user can play back MP3 Files. The code looks something like this:
Response.Clear();
Response.ContentType = "audio/mpeg";
foreach (DataChunk leChunk in db.Mp3Files.First(mp3 => mp3.Mp3ResourceId.Equals(id)).Data.Chunks.OrderBy(chunk => chunk.ChunkOrder))
{
Response.BinaryWrite(leChunk.Data);
}
Unfortunately, if a larger MP3 file is selected, the audio does not begin to play until the entire file is downloaded, which can cause a noticeable delay. Is there any way to get the MP3 to start playing immediately, even though the entire file may not yet be transferred?
You should be able to do what you want by writing to the outpstream of the response, i.e.:
Response.OutputStream.Write
It is also probably a good idea to check previously if Response.IsClientConnected and give up if not.
I found a demo that allows playback of mp3 files from an asp.net web application:
http://aspsnippets.com/Articles/Save-MP3-Audio-Files-to-database-and-display-in-ASPNet-GridView-with-Play-and-Download-option.aspx
try this:
Response.BufferOutput = false; //sets chunked encoding
Response.ContentType = "audio/mpeg";
using (var bw = new BinaryWriter(Response.OutputStream))
{
foreach (DataChunk leChunk in db.Mp3Files.First(mp3 => mp3.Mp3ResourceId.Equals(id)).Data.Chunks.OrderBy(chunk => chunk.ChunkOrder))
{
if (Response.IsClientConnected) //avoids the host closed the connection exception
{
bw.Write(leChunk.Data);
}
}
}
Also, go yo your web.config file and do this if you still have problems with chunked encoding:
<system.webServer>
<asp enableChunkedEncoding="true" />
</system.webServer>
The error you reported above about the host being closing the connection is happening probably because you are opening the page using the browser and when the browser reads the content type, it opens the media player and closes itself who had the opened connection which was then closed, causing that error, so to avoid this, you need to check periodically whether your client is still connected or not.
Finally, I would use a Generic Handler (.ashx) or a custom handler and set a .mp3 extension for this if you are using a aspx page to avoid the unnecessary overhead of the web page.
I hope this helps.
Try setting Response.BufferOutput = false before streaming the response.
If the location of the MP3 files are publicly available to your user then an alternative approach could be to just return the MP3's URL and use the HTML 5 audio tags in your mark up to stream the music. I am pretty sure that the default behaviour of the audio tag would be to stream the file rather than wait until the whole file has downloaded.
One method to support this would be implementing HTTP byte range requests.
By default I don't believe that ASP.NET does this, and definitely won't if using any of the code in the questions or the answer.
You can implement this manually with a little work though. Another option, which would be much less dev work, would be to let IIS serve a static file. I assume that isn't an option though.
Here's an example implementation:
http://www.codeproject.com/Articles/820146/HTTP-Partial-Content-In-ASP-NET-Web-API-Video

How not to abort http response c#

I need to run several methods after sending file to a user for a download. What happens is that after I send a file to a user, response is aborted and I can no longer do anything after response.end().
for example, this is my sample code:
Response.Clear();
Response.AddHeader("content-disposition", "attachment; filename=test.pdf");
Response.ContentType = "application/pdf";
byte[] a = System.Text.Encoding.UTF8.GetBytes("test");
Response.BinaryWrite(a);
Response.End();
StartNextMethod();
Response.Redirect(URL);
So, in this example StartNextMethod and Response.Redirect are not executing.
What I tried is I created a separate handler(ashx) with the following code:
public void ProcessRequest(HttpContext context)
{
context.Response.Clear();
context.Response.AddHeader("content-disposition", "attachment; filename=test.pdf");
context.Response.ContentType = "application/pdf";
byte[] a = System.Text.Encoding.UTF8.GetBytes("test");
context.Response.BinaryWrite(a);
context.Response.End();
}
and call it like this:
Download d = new Download();
d.ProcessRequest(HttpContext.Current);
StartNextMethod();
Response.Redirect(URL);
but the same error happen. I've tryied to replace Response.End with CompleteRequest but it doesn't help.
I guess the problem is that I'm using HttpContext.Current but should use a separate response stream. Is that correct? how do I do that in a separate method generically (Assume that I want my handler to accept byte array of data and content type and be downloadable from a separate response. I really do not want to use a separate page for a response.
UPDATE
I still didn't find a good solution. I'd like to do some actions after user has downloaded a file, but without using a separate page for a response\request thing.
Update
Since you said no second page, do this instead. Add a section to your page that checks for a query string parameter (something like fileid, or path, etc...). If this value is present then it initiates the download process using your existing code. If this value is not present then it runs like normal.
Now when the user clicks the download link you perform a post back (which you are already doing). In this post back create an iFrame on the page and set the URL of the iFrame to your pages URL with the added query string parameter (mypage.aspx?id=12664 or ?download=true, something like that). After creating the iframe perform what ever additional databinds/etc... you wish too.
Example
- http://encosia.com/ajax-file-downloads-and-iframes/
This above linked example uses an iFrame and an update panel, just like you are talking about.
Original Post
Response.Flush will allow you to continue processing after you send the file to the user, or just don't call Response.End (you don't really need too).
However Daniel A. White is correct, you can't actually redirect from your code after you send a file, you will get an error if you try. BUT you can continue to perform other server side operations if you need to.
Other answers agree with the general consensus, you can't redirect after a file starts downloading: https://stackoverflow.com/a/822732/328968 (PHP, but same concepts since it involves HTTP in general). or Directing to a new page after downloading a file.
Response.End() throws a thread abort exception. It is designed to end your response.
No code after that will process in that thread.
The End method causes the Web server to stop processing the script and return the current result. The remaining contents of the file are not processed.
What is it that you are trying to achieve?
If your purpose it to allow the pdf to download and then take the user to some other page, a little javascript can help you out.
Add a script with a timer to set location.href to your redirected paged.
As the previous answers had stated - returning PDF file means to send HTTP headers. You cannot send another headers after that, and Response.Redirect() simply means to send HTTP 302.
If you don't want to have separate page, or if you don't want to use AJAX, why not trying:
<head>
<meta http-equiv="refresh" content="3; url=http://www.site.com/download.aspx?xxxx">
</head>
Actually this will show the desired page you want to show to the user, and will refresh the page after 3 sec with the URL for download of the PDF file.
Download the file in chunks, as illustrated File Download in ASP.NET and Tracking the Status of Success/Failure of Download or in the answer to this question. When the last chunk of the file has been written to the client you can execute the code you need to. (Doesn't have to be at the end, can be anywhere in between depending upon your needs.)
the user clicks on a download button on WebForm1.aspx to start downloading a file. then, after the file download is done (served by WebForm2.aspx), user is automatically redirected.
WebForm1.aspx
<script type="text/javascript">
$(document).ready(function () {
$('#btnDL').click(function () {
$('body').append('<iframe src="WebForm2.aspx" style="display:none;"></iframe>');
return true;
});
});
</script>
<asp:Button runat="server" ID="btnDL" ClientIDMode="Static" Text="Download" OnClick="btnDL_Click" />
WebForm1.aspx.cs
protected void btnDL_Click(object sender, EventArgs e)
{
var sent = Session["sent"];
while (Session["sent"]==null)
{// not sure if this is a bad idea or what but my cpu is NOT going nuts
}
StartNextMethod();
Response.Redirect(URL);
}
WebForm2.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
Response.Clear();
Response.AddHeader("content-disposition", "attachment; filename=test.pdf");
Response.ContentType = "application/pdf";
byte[] a = System.Text.Encoding.UTF8.GetBytes("test");
Response.BinaryWrite(a);
Session["sent"] = true;
}
Global.asax.cs
protected void Session_Start(object sender, EventArgs e)
{
Session["init"] = 0; // init and allocate session data storage
}
note: make sure don't use ashx (generic handler) to serve your download. for some reason, the session in ashx and aspx don't talk to each other, unless you implement this.
Just remove the context.Response.End(); because you are redirecting anyway...
The problem is flawed logic here.... Why would you end the response?
Get the PDF and display a link to it or use a META refresh to redirect to the location of the PDF or you could also display a link or use a combination of both techniques.
I believe what you are trying won't work.
This is what I would do:
Write content to a file locally and assign it an unique id
send user to the next page that contains a hidden frame that perform a request with the unique id (javascript)
hidden request page loads file and push on the content stream.
This is the same behavior a lot of file download sites is using. Only issue is if the hidden frame fails (javascript turned off) to perform the request, why a lot of the same sites have the link available if the auto request fails.
Disadvantage: file cleanup.
I recommend this solution :
Don't use response.End();
Declare this global var : bool isFileDownLoad;
Just after your (Response.BinaryWrite(a);) set ==> isFileDownLoad = true;
Override your Render like :
///
/// AEG : Very important to handle the thread aborted exception
///
///
override protected void Render(HtmlTextWriter w)
{
if (!isFileDownLoad) base.Render(w);
}

Code fails to download a file more than once

Whenever I click a button in my GUI this code gets executed
this.file_name = #"c:\temp\file_" + DateTime.Now.Ticks / 10000 +".pdf";
client.DownloadFileCompleted +=
new AsyncCompletedEventHandler(pdfDownloadComplete);
client.DownloadFileAsync(new Uri(uri), file_name);
It's supposed to download a pdf file. It works fine the first time I click it, but the second time all it does is it creates an empty file in the temp directory and downloads nothing, I click the same button afterwards nothing new happens.
I cannot figure it out why it won't download more than once.
Later Edit
This is the complete code which is needed http://pastie.org/private/y7na2f4fjdu6anzteoa
I noticed that if I remove the download that checks for content type, the app downloads files without a problem
client.HeadOnly = true;
byte[] body = client.DownloadData(uri); // note should be 0-length
string type = client.ResponseHeaders["content-type"];
client.HeadOnly = false;
Still, I need to know if I'm getting text of a file from that URL so I need to make that call.
Your code has at least one issue that might be root of the problem:
You do an async download inside a using block. I don't really know what happens if the download is still running when the scope of the using block is left, but I guess that it is canceled. You should avoid that problem by using DownloadFile instead of DownloadFileAsync.
Additionally, please check whether it works, when you use the normal WebClient class and not your MyClient class.

download a file from server and then delete on server

ok i am downloading a file from a server and i plan to delete the file that i have downloaded on the server after it gets downloaded on the client side..
My download code is working fine but i dont know when to put the command to delete the file.
string filepath = restoredFilename.ToString();
// Create New instance of FileInfo class to get the properties of the file being downloaded
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);
//Response.AddHeader("Content-Disposition", "inline; filename=" + myfile.Name);
// Add the file size into the response header
Response.AddHeader("Content-Length", myfile.Length.ToString());
// Set the ContentType
Response.ContentType = ReturnExtension(myfile.Extension.ToLower());
//// Write the file into the response (TransmitFile is for ASP.NET 2.0. In ASP.NET 1.1 you have to use WriteFile instead)
Response.TransmitFile(myfile.FullName);
// End the response
Response.End();
}
Now i know the response.End() will stop every thing and return the value, so is there another way too do so..
I need to call a function
DeleteRestoredFileForGUI(restoredFilename);
to delete the file but dont know where to put it.. i tried putting before and after Response.End() but it does not work..
any help is appreciated... thanks
Add
Response.Flush();
DeleteRestoredFileForGUI(restoredFilename);
after the call to TransmitFile() and ditch the call to Response.End() (you don't need it).
If that does not work, then ditch TransmitFile() and go with:
Stream s = myFile.OpenRead();
int bytesRead = 0;
byte[] buffer = new byte[32 * 1024] //32k buffer
while((bytesRead = s.Read(buffer, 0, buffer.Length)) > 0 &&
Response.IsClientConnected)
{
Response.OutputStream.Write(buffer, 0, bytesRead);
Response.Flush();
}
you can't delete the file straight away as it may not have been downloaded yet. from the server side there is no easy way of telling that the file was successfully downloaded. what if an open/save dialog is opened by the browser? download won't begin until the dialog is acknowledged. (this may not be immediately and/or the dialog may be cancelled)
or, what if it is a large file and the connection is dropped before it is fully downloaded? should it be possible to attempt the download again?
the normally recommended way of dealing with your situation is to do the deletion as a separate process, after a time period which allows you to be (fairly) sure the file is no longer required and/or it can be recreated/restored if need be.
depending on your situation you could have a separate process which periodically removes/processes old files. or, if you have a low volume of traffic, you could check for and delete old files each time a new one is requested.
the identification of old files will likely be based on a file time or associated value in a darabase. either way, if there are potentially lots of files to process you are unlikely to want the overhead of checking very frequently if it is unlikely to identify a lot of files to remove.
also, be sure to way up the consequences of lots of files not being removed ASAP (is disk space really an issue?) against the side effects of possibly deleting them while still needed or creating a performance side effect by checking to zealously.
The general pattern you are following makes me wonder, are you doing this?
Create Data for Client and Save to
Disk Transmit File to Client Delete
File
If you are, you might change your system to work in memory. Since memory is managed in .Net you wouldn't have to do this manual cleanup, and depending on the size of the file this could be a good bit faster too:
Create Data for Client and Save to MemoryStream
Transmit Stream to Client
Since you set the file name in the header, you have two options:
Read the file contents into a string, delete the file, echo/print the string as the body of the message.
Rename the file something like delete-filename.xxx and then have some external process (maybe a cron job?) that goes behind and deletes any files beginning with that prefix.

Categories