Невозможно удалить временные файлы после возврата 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
экземпляра.
когда этот класс-обертка Dispose
D, вы бы (после Dispose
ing завернутый 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"
};
}