Cannot delete temporary files after returning FileStream

I have a function in a C # MVC application that creates a temporary directory and a temporary file, then opens the file using FileStream, returns the FileStream to the calling function, and then deletes the temporary files. However, I do not know how to delete the temp directory and file because it always mistakenly says that "the process cannot access the file because it is being used by another process." This is what I tried, but FileStream still uses the temporary file in the finally block. How can I return a FileStream and delete temporary files?

public FileStream DownloadProjectsZipFileStream() { Directory.CreateDirectory(_tempDirectory); // temporary file is created here _zipFile.Save(_tempDirectory + _tempFileName); try { FileStream stream = new FileStream(_tempDirectory + _tempFileName, FileMode.Open); return stream; } finally { File.Delete(_tempDirectory + _tempFileName); Directory.Delete(_tempDirectory); } } 

The returned FileStream function looks like this:

 public ActionResult DownloadProjects () { ProjectDownloader projectDownloader = new ProjectDownloader(); FileStream stream = projectDownloader.DownloadProjectsZipFileStream(); return File(stream, "application/zip", "Projects.zip"); } 

Update: I forgot to mention that the zip file is 380 MB. I get a memory exception when using a MemoryStream.

+7
source share
5 answers

You can create a wrapper class that implements the Stream contract and contains the FileStream inside, as well as saving the path to the file.

All standard Stream methods and properties will simply be passed to the FileStream instance.

If this shell class is Dispose d, you can (after Dispose wrap FileStream ) and then delete the file.

+4
source

Here is the version below that I am using:

 public class DeleteAfterReadingStream : FileStream { public DeleteAfterReadingStream(string path) : base(path, FileMode.Open) { } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (File.Exists(Name)) File.Delete(Name); } } 
+4
source

I used the advice of Damien_The_Unbeliever (accepted answer), wrote it, and it worked beautifully. Just thought I'd split the class:

 public class BurnAfterReadingFileStream : Stream { private FileStream fs; public BurnAfterReadingFileStream(string path) { fs = System.IO.File.OpenRead(path); } public override bool CanRead { get { return fs.CanRead; } } public override bool CanSeek { get { return fs.CanRead; } } public override bool CanWrite { get { return fs.CanRead; } } public override void Flush() { fs.Flush(); } public override long Length { get { return fs.Length; } } public override long Position { get { return fs.Position; } set { fs.Position = value; } } public override int Read(byte[] buffer, int offset, int count) { return fs.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { return fs.Seek(offset, origin); } public override void SetLength(long value) { fs.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { fs.Write(buffer, offset, count); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (Position > 0) //web service quickly disposes the object (with the position at 0), but it must get rebuilt and re-disposed when the client reads it (when the position is not zero) { fs.Close(); if (System.IO.File.Exists(fs.Name)) try { System.IO.File.Delete(fs.Name); } finally { } } } } 
+3
source

The problem is that you can only delete a file after it has been written in the response, and the file is written by FileStreamResult only after it has been returned from the action.

One way to handle this is to create a subclass of FileResult that will delete the file.

This is easier for the FilePathResult subclass FilePathResult that the class has access to the file name.

 public class FilePathWithDeleteResult : FilePathResult { public FilePathResult(string fileName, string contentType) : base(string fileName, string contentType) { } protected override void WriteFile(HttpResponseBase response) { base.WriteFile(response); File.Delete(FileName); Directory.Delete(FileName); } } 

Note. I have not tested the above. Remove all errors before use.

Now change the controller code to something like:

 public ActionResult DownloadProjects () { Directory.CreateDirectory(_tempDirectory); // temporary file is created here _zipFile.Save(_tempDirectory + _tempFileName); return new FilePathWithDeleteResult(_tempDirectory + _tempFileName, "application/zip") { FileDownloadName = "Projects.zip" }; } 
+2
source

I use the method suggested by @hwiechers, but the only way to make it work is to close the response stream before deleting the file.

Here is the source code, please note that I clear the stream before deleting it.

 public class FilePathAutoDeleteResult : FilePathResult { public FilePathAutoDeleteResult(string fileName, string contentType) : base(fileName, contentType) { } protected override void WriteFile(HttpResponseBase response) { base.WriteFile(response); response.Flush(); File.Delete(FileName); } } 

And this is how the controller should call it:

 public ActionResult DownloadFile() { var tempFile = Path.GetTempFileName(); //do your file processing here... //For example: generate a pdf file return new FilePathAutoDeleteResult(tempFile, "application/pdf") { FileDownloadName = "Awesome pdf file.pdf" }; } 
0
source

All Articles