8

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 a calling function, and then needs to delete the temporary files. However, I do not know how to delete the temp directory and file because it always errors out saying "the process cannot access the file because it is being used by another process." This is what I tried, but the FileStream is still using the temp file in the finally block. How can I return the FileStream and delete the 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 function the FileStream is returned to 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 the zip file is 380 MB. I get a system out of memory exception when using a MemoryStream.

5
  • The FileStream has the file open, why do you even expect to be able to delete the file? That would make the returned FileStream unusable. Commented Apr 10, 2013 at 18:52
  • What is the _zipFile's type?
    – Peter Kiss
    Commented Apr 10, 2013 at 18:54
  • 2
    You REALLY don't need the file, save to a memory stream instead. Commented Apr 10, 2013 at 18:57
  • Using a memory stream allows me to delete the temporary files, but I get a 'system out of memory exception' when copying the filestream to a memorystream. The zip file is 380 MB by the way.
    – Bumper
    Commented Apr 10, 2013 at 19:22
  • 2
    @WiktorZychla Using a MemoryStream is horribly inefficient in terms of memory usage for large files, especially if the target only needs to process the file instead of reading it all in; and if it does need to read it all in, it would result in double memory usage. Commented Apr 10, 2013 at 19:34

5 Answers 5

8

Here's a cutdown version of the above that I use:

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

You could create a wrapper class that implements the Stream contract and that contains the FileStream internally, as well as maintaining the path to the file.

All of the standard Stream methods and properties would just be passed to the FileStream instance.

When this wrapper class is Disposed, you would (after Disposeing the wrapped FileStream) then delete the file.

1
  • This is what I ended up doing. I just extended the FileStream and in the constructor passed in the temp file/directory paths, and in the Dispose method I added the delete code for the temp file and directory. Thanks for the suggestion!
    – Bumper
    Commented Apr 11, 2013 at 15:31
5

I used Damien_The_Unbeliever's (accepted answer)'s advice, wrote it up, and it worked beautifully. Just thought I'd share 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 { }
        }
    }
}
1

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

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

It's easier to subclass FilePathResult so that the class has access to the filename.

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 haven't tested the above. Remove all it's bugs before using it.

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" };
}
1
  • This doesn't work unfortunately. It gives a "The handle is invalid" exception. Commented Mar 3, 2019 at 11:20
1

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, note that I flush 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 here is how the Controller supposed to 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"
    };
}
1
  • Brilliant. The response.Flush() made @hwiechers solution work indeed. Commented Mar 3, 2019 at 11:27

Not the answer you're looking for? Browse other questions tagged or ask your own question.