2013-04-10 15 views
7

Mam funkcję w aplikacji C# MVC, która tworzy katalog tymczasowy i plik tymczasowy, a następnie otwiera plik przy użyciu FileStream, zwraca obiekt FileStream do funkcji wywołującej, a następnie musi usuń pliki tymczasowe. Jednak nie wiem, jak usunąć katalog tymczasowy i plik, ponieważ zawsze błędnie mówi "proces nie może uzyskać dostępu do pliku, ponieważ jest używany przez inny proces." Oto, co próbowałem, ale FileStream nadal używa pliku tymczasowego w bloku finally. Jak mogę zwrócić plik FileStream i usunąć pliki tymczasowe?Nie można usunąć plików tymczasowych po zwróceniu pliku 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); 
    } 
} 

Funkcja FileStream jest zwracana wygląda następująco:

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

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

Aktualizacja: Zapomniałem wspomnieć plik zip jest 380 MB. Podczas korzystania z obiektu MemoryStream otrzymuję wyjątek dotyczący braku pamięci systemowej.

+0

Opcja 'FileStream' ma otwarty plik, dlaczego nawet oczekiwać, aby móc usunąć plik? To spowodowałoby, że zwrócone 'FileStream' nie nadaje się do użytku. –

+0

Jaki jest typ pliku _zipFile? –

+2

NAPRAWDĘ nie potrzebujesz tego pliku, zamiast tego zapisz w strumieniu pamięci. –

Odpowiedz

4

Można by utworzyć klasy otoki, który implementuje umowy Stream i że zawiera FileStream wewnętrznie, jak również utrzymanie ścieżkę do pliku.

Wszystkie standardowe metody i właściwości o standardach Stream zostałyby przekazane do instancji FileStream.

Gdy ta klasa otoki to Dispose d, należy (po Dispose ing opakowanej FileStream), a następnie usunąć plik.

+0

Tak właśnie zrobiłem. Właśnie rozszerzyłem FileStream oraz w konstruktorze przekazanym w ścieżkach katalogu/plików tymczasowych, aw metodzie Dispose dodałem kod usuwania dla pliku tymczasowego i katalogu. Dzieki za sugestie! – Bumper

+1

@Bumber, Czy umieścisz swój kod? – RayLoveless

2

Problem polega na tym, że można usunąć plik tylko po jego zapisaniu w odpowiedzi, a plik zostanie zapisany przez obiekt FileStreamResult dopiero po zwróceniu go z działania.

Jednym ze sposobów obsługi jest utworzenie podklasy FileResult, która usunie plik.

Łatwiej jest podklasę FilePathResult, aby klasa miała dostęp do nazwy pliku.

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

Uwaga: Nie testowałem powyższego. Usuń wszystkie błędy przed jego użyciem.

teraz zmienić kod kontrolera do czegoś podobnego:

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

Użyłem rady Damien_The_Unbeliever's (zaakceptowanej odpowiedzi), napisałem ją i działało pięknie. Pomyślałem, że mogę dzielić klasę:

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

Oto wersja nacięcia z powyższego, że używam:

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

używam metody zaproponowanej przez @hwiechers, ale to jedyny sposób, aby go praca polega na zamknięciu strumienia odpowiedzi przed usunięciem pliku.

Oto kod źródłowy, należy pamiętać, że należy przepłukać strumień przed usunięciem.

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

} 

A oto jak kontroler powinien nazywać:

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" 
    }; 
} 
Powiązane problemy