download a file from server and then delete on server - c#

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.

Related

How to show progress of zip download without zip file existing before hand but knowing the file sizes

I have logic that downloads a group of files as a zip. The issue is there is no progress so the user does not know how far along the download is.
This Zip file doesn't exist before hand, the user selects the files they want to download and then I use the SharpZipLib nuget package to create a zip
and stream it to the response.
It seems I need to set the Content-Length header for the browser to show a total size progress indicator. The issue I'm having is it seems
this value has to be exact, if its too low or too high by 1 byte the file does not get downloaded properly. I can get an approximate
end value size by adding all the files size together and setting there to be no compressions level but I don't see a way I can calculate the final zip size exactly.
I hoped I could of just overesitmated the final size a bit and the browser would allow that but that doesn't work, the file isn't downloaded properly so you cant access it.
Here are some possible solution I've come up with but they have there own issues.
1 - I can create the zip on the server first and then stream it, therefore knowing the exact size I can set the Content-length. Issue with this
is the user will have to wait for all the files to be streamed to the web server, the zip to be created and then I can start streaming it to the user. While this is going on the user wont even see the file download as being started. This also results in more memory usage of the web server as it has to persist the entire zip file in memory.
2 - I can come up with my own progress UI, I will use the combined file sizes to get a rough final size estimation and then as the files are streamed I push updates to the user via signalR indicating the progress.
3- I show the user the total file size before download begins, this way they will at least have a way to assess themselves how far along it is. But the browser has no indication of how far along it is so if they may forget and when they look at the browser download progress there will be no indication how far along it is
These all have their own drawbacks. Is there a better way do this, ideally so its all handled by the browser?
Below is my ZipFilesToRepsonse method. It uses some objects that aren't shown here for simplicity sake. It also streams the files from azure blob storage
public void ZipFilesToResponse(HttpResponseBase response, IEnumerable<Tuple<string,string>> filePathNames, 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";
Dictionary<string,long> sizeDictionary = new Dictionary<string, long>();
long totalSize = 0;
foreach (var file in filePathNames)
{
long size = GetBlobProperties(file.Item1).Length;
totalSize += size;
sizeDictionary.Add(file.Item1,size);
}
//Zip files breaks if we dont have exact content length
//and it isn't nesccarily the total lengths of the contents
//dont see a simple way to get it set correctly without downloading entire file to server first
//so for now we wont include a content length
//response.AddHeader("Content-Length",totalSize.ToString());
foreach (var file in filePathNames)
{
long size = sizeDictionary[file.Item1];
var entry = new ZipEntry(file.Item2)
{
DateTime = DateTime.Now,
Size = size
};
zipOutputStream.PutNextEntry(entry);
Container.GetBlockBlobReference(file.Item1).DownloadToStream(zipOutputStream);
response.Flush();
if (!response.IsClientConnected)
{
break;
}
}
zipOutputStream.Finish();
zipOutputStream.Close();
}
response.End();
}

c# downloading a zip archieve of files created by web application

using Ionic.Zip
...
using (ZipFile zip = new ZipFile())
{
zip.AlternateEncodingUsage = ZipOption.AsNecessary;
zip.AddDirectoryByName("Files");
foreach (GridViewRow row in GridView1.Rows)
{
if ((row.FindControl("chkSelect") as CheckBox).Checked)
{
string filePath = (row.FindControl("lblFilePath") as Label).Text;
zip.AddFile(filePath, "Files");
}
}
Response.Clear();
Response.BufferOutput = false;
string zipName = String.Format("Zip_{0}.zip", DateTime.Now.ToString("yyyy-MMM-dd-HHmmss"));
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "attachment; filename=" + zipName);
zip.Save(Response.OutputStream);
Response.End();
}
Hello! This portion of code does the downloading of a zipped directory. Let's say I have a gridview of CONTENTS of text files I want to download. Is there a way of making the program download such archieve without knowing or writing the paths to files?
The code should work this way:
1. get item from gridview
2. create a text file from the content
3. add it to the zip directory
(repeat foreach item in gridview)
n. download a zipped file
According to the documentation, you can add an entry from a Stream. So consider where you currently do this:
zip.AddFile(filePath, "Files");
Instead of adding a "file" given a path, you'd add a "file" given a stream of data.
So you can create a stream from a string:
new MemoryStream(Encoding.UTF8.GetBytes(someString)) // or whatever encoding you use
and add it to the Zip:
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(someString)))
{
zip.AddEntry(someFileName, stream);
// other code
zip.Save(Response.OutputStream);
}
One thing to note here is that your resource management and disposal (with the using blocks) might get a little tricky. This is because, according to the documentation:
The application should provide an open, readable stream; in this case it will be read during the call to Save() or one of its overloads.
What this means is that if you dispose of any of the streams before calling .Save(), it will fail when you call it. You might want to look through the documentation some more to see if there's a way to force the Zip to read the streams earlier in the process. Otherwise you're basically going to have to manage a bunch of open streams until it's time to "save" the Zip.
Edit: It looks like the documentation was right there...
In cases where a large number of streams will be added to the ZipFile, the application may wish to avoid maintaining all of the streams open simultaneously. To handle this situation, the application should use the AddEntry(String, OpenDelegate, CloseDelegate) overload.
This will be a little more complex and will require you to open/close/dispose your streams manually in your delegates. So it's up to you as you build your logic whether this is preferable to nesting your using blocks. It'll likely depend on how many streams you plan to use.

100s of concurrent users trying to download files (asp.net C# application)

I am trying to implement file download feature in asp.net application. The application would be used by say around 200 users concurrently to download various files.
It would be hosted on IIS 7. I do not want the application server to crash because of multiple requests coming concurrently.
I am assuming that by calling Context.Response.Flush() in a loop, I am flushing out all the file data that I would have read till then, so application memory usage would be kept uniform. What other optimizations can I make to the current code or what other approach should be used in a scenario like this?
The requests would be for various files and the file sizes can be anywhere between 100 KB to 10 MB.
My current code is like this:
FileStream inStr = null;
byte[] buffer = new byte[1024];
String fileName = #"C:\DwnldTest\test.doc";
long byteCount; inStr = File.OpenRead(fileName);
Response.AddHeader("content-disposition", "attachment;filename=test.doc");
while ((byteCount = inStr.Read(buffer, 0, buffer.Length)) > 0)
{
if (Context.Response.IsClientConnected)
{
Context.Response.ContentType = "application/msword";
//Context.Response.BufferOutput = true;
Context.Response.OutputStream.Write(buffer, 0, buffer.Length);
Context.Response.Flush();
}
}
You can use Response.TransmitFile to save server memory when sending files.
Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "attachment; filename=testdoc.pdf");
Response.TransmitFile(#"e:\inet\www\docs\testdoc.pdf");
Response.End();
In your code example, you're not closing / disposing inStr. That could affect performance.
Another more simple way to do this would be to use the built in method:
WriteFile
It should already be optimized and will take care of opening / closing files for you.
Maybe you want to use FileSystemWatcher class to check if the file was modified, and read it into memory only while such change was detected. For rest of the time just return the byte array that is already stored in memory. I don't know if HttpResponse.WriteFile method is sensitive for such file modification changes, or if always reads a file from given path, but this also seems to be a good option to use, as it is served by framework out of the box.
Since you are sending an existing file to the client, consider using HttpResponse.TransmitFile (http://msdn.microsoft.com/en-us/library/12s31dhy.aspx).
Looking at the .NET code it seems that this will forward the file writing to IIS instead of reading/writing it in ASP.NET process. HttpResponse.WriteFile(string, false) and HttpResponse.Write(string) seems to do the same thing.
In order to verify that the file sending is relayed to IIS, at HttpResponse.Output property - it should be of type HttpWriter. The HttpWriter._buffers array should now contain a new element HttpFileResponseElement).
Of course, you should always investigate if caching is appropriate in your scenario and test if it is being used.

View PDF in Browser in new window on button click and delete file

I am having issue with deleting file created just to send an email with attachment and then view it in browser. now i need to delete this file as this is created to just send email. how can i do this.
here is what i have got so far.
public void SendEmail()
{
EmailClient.Send(mailMessage);
//View PDF Certificate in Browser
ViewPDFinBrowser((string)fileObject);
DeleteGeneratedTempCertificateFile((string)fileObject));
}
public void ViewPDFinBrowser(string filePath)
{
PdfReader reader = new PdfReader(filePath);
MemoryStream ms = new MemoryStream();
PdfStamper stamper = new PdfStamper(reader, ms);
stamper.ViewerPreferences = PdfWriter.PageLayoutSinglePage | PdfWriter.PageModeUseThumbs;
stamper.Close();
Response.Clear();
Response.ContentType = "application/pdf";
Response.OutputStream.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
Response.OutputStream.Close();
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
public static void DeleteGeneratedTempCertificateFile(Object fileObject)
{
string filePath = (string)fileObject;
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
So here are the steps i need when i call SendEmail()
1) Sends an email with the attachment --> Temp file created
2) view the temp file in the browser
3) delete the temp file
I can understand that as long as file is in response object, i can not do anything with it because i get the error message ("File used by another process). If i close the response stream then file will be deleted but then i cant view it in browser.
i was thinking if i can manage to somehow open the file to view in browser in new window on button click, i will be able to delete the file.
OR
i am thinking i can delete the file after 10 min. as user wont be on site viewing the PDF for more then 1-2 mins.
please advice me one of the solution with example code.
appreciate your time and help.
As others have said, it's better to use the MemoryStream as-is without writing temporary files to the disk. Sometimes implementations of 3rd party components just won't allow this and in such cases after writing the binary contents of the PDF file, be sure to call close (and/or possibly dispose, always check MSDN or 3rd party API docs what the .Close() actually does) to all streams that are no longer needed. In your case close ms and reader after completing the http request.
In most cases, consider implementing the using pattern. See http://msdn.microsoft.com/en-us/library/aa664736.aspx for more details. However remember that there are caveats to this approach too, for example in WCF clients which can cause exceptions within (and thus not actually disposing all contents inside the using clause).
Also, keep in mind any concurrency issues. Keep the temporary file name random enough and consider situations where the file already exists on the local disk (i.e. fail the operation and do not send out binary to the request which the requester is not supposed to see etc).

Some questions about writing on ASP.NET response stream

I'm making tests with ASP.NET HttpHandler for download a file writting directly on the response stream, and I'm not pretty sure about the way I'm doing it. This is a example method, in the future the file could be stored in a BLOB in the database:
public void GetFile(HttpResponse response)
{
String fileName = "example.iso";
response.ClearHeaders();
response.ClearContent();
response.ContentType = "application/octet-stream";
response.AppendHeader("Content-Disposition", "attachment; filename=" + fileName);
using (FileStream fs = new FileStream(Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data"), fileName), FileMode.Open))
{
Byte[] buffer = new Byte[4096];
Int32 readed = 0;
while ((readed = fs.Read(buffer, 0, buffer.Length)) > 0)
{
response.OutputStream.Write(buffer, 0, readed);
response.Flush();
}
}
}
But, I'm not sure if this is correct or there is a better way to do it.
My questions are:
When I open the url with the browser, appears the "Save File" dialog... but it seems like the server has started already to push data into the stream before I click "Save", is that normal?
If I remove the line"response.Flush()", when I open the url with the browser, ... I see how the web server is pushing data but the "Save File" dialog doesn't come up, (or at least not in a reasonable time fashion) why?
When I open the url with a WebRequest object, I see that the HttpResponse.ContentLength is "-1", although I can read the stream and get the file. What is the meaning of -1? When is HttpResponse.ContentLength going to show the length of the response? For example, I have a method that retrieves a big xml compresed with deflate as a binary stream, but in that case... when I access it with a WebRequest, in the HttpResponse I can actually see the ContentLength with the length of the stream, why?
What is the optimal length for the Byte[] array that I use as buffer for optimal performance in a web server? I've read that is between 4K and 8K... but which factors should I consider to make the correct decision.
Does this method bloat the IIS or client memory usage? or is it actually buffering the transference correctly?
Sorry for so many questions, I'm pretty new in web development :P
Cheers.
Yes; this is normal.
If you never flush, the browser doesn't get any response until the server finishes (Not even the Content-Disposition header). Therefore, it doesn't know to show a file dialog.
The Content-Length header only gets set if the entire response is buffered (If you never flush) or if you set it yourself. In this case, you can and should set it yourself; write
response.AppendHeader("Content-Length", new FileInfo(path).Length.ToString());
I recommend 4K; I don't have any hard basis for the recommendation.
This method is the best way to do it. By calling Flush inside the loop, you are sending the response down the wire immediately, without any buffering. However, for added performance, you can use GZIP compression.
Yes, it is buffering.
Flush pushes the cached content to the browser. If it is never pushed, you won't get a save dialog box.
Hard to tell without seeing the exact files/URLs/Streams you are using.
I think the factors depends on how sluggish your page is, really. You will have better performance toward 4k. And perhaps, the lower value will be better to accommodate slower connections.
See #1 & 2.
For #3 you need to set the content-length header in your http-response. Many of those values come from http headers.
I believe you can change the bufferring by changing a buffering property on the response object to false. Haven't done it in a while so I don't remember what it might be.

Categories