Невозможно удалить временные файлы после возврата FileStream

у меня есть функция в приложении C# MVC, которая создает временный каталог и временный файл, затем открывает файл с помощью FileStream, возвращает FileStream вызывающей функции, а затем необходимо удалить временные файлы. Однако я не знаю, как удалить каталог temp и файл, потому что он всегда ошибается, говоря: "процесс не может получить доступ к файлу, потому что он используется другим процессом."Это то, что я пытался, но FileStream все еще использует временный файл в блок finally. Как вернуть FileStream и удалить временные файлы?

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

функция FileStream возвращается выглядит следующим образом:

public ActionResult DownloadProjects ()
{
    ProjectDownloader projectDownloader = new ProjectDownloader();

    FileStream stream = projectDownloader.DownloadProjectsZipFileStream();
    return File(stream, "application/zip", "Projects.zip");
}

обновление: Я забыл упомянуть, что zip-файл составляет 380 МБ. Я получаю исключение системы из памяти при использовании MemoryStream.

5 ответов


вы можете создать класс-оболочку, который реализует Stream контракт и который содержит FileStream внутренне, а также поддержание пути к файлу.

все стандартные Stream методы и свойства будут просто переданы в FileStream экземпляра.

когда этот класс-обертка DisposeD, вы бы (после Disposeing завернутый FileStream), то удалите файл.


вот сокращенная версия выше, которую я использую:

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

я использовал совет Damien_The_Unbeliever (принятый ответ), написал его, и он работал красиво. Просто подумал, что поделюсь классом:

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

проблема в том, что вы можете удалить файл только после того, как он был записан в ответ, и файл записывается FileStreamResult только после того, как он возвращается из действия.

один из способов обработки-создать подкласс FileResult, который удалит файл.

легче подкласс FilePathResult чтобы класс имел доступ к имени файла.

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

примечание: Я не тестировал выше. Удалите все ошибки перед использованием он.

теперь измените код контроллера на что-то вроде:

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

Я использую метод, предложенный @hwiechers, но единственный способ заставить его работать-закрыть поток ответов перед удалением файла.

вот исходный код, обратите внимание, что я очищаю поток перед его удалением.

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

}

и вот как контроллер должен его называть:

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