2013-04-10 23 views
7

Ho una funzione in un'applicazione C# MVC che crea una directory temporanea e un file temporaneo, quindi apre il file utilizzando FileStream, restituisce FileStream a una funzione di chiamata e quindi deve elimina i file temporanei. Tuttavia, non so come eliminare la directory temporanea e il file perché si sbaglia sempre dicendo "il processo non può accedere al file perché è utilizzato da un altro processo". Questo è quello che ho provato, ma FileStream sta ancora utilizzando il file temporaneo nel blocco finally. Come posso restituire FileStream ed eliminare i file temporanei?Impossibile eliminare i file temporanei dopo il ritorno 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); 
    } 
} 

La funzione del FileStream viene restituito al assomiglia a questo:

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

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

Aggiornamento: ho dimenticato di menzionare il file zip è di 380 MB. Ricevo un'eccezione di memoria del sistema quando utilizzo un MemoryStream.

+0

Il 'FileStream' ha il file aperto, perché si aspetta anche di essere in grado di cancellare il file? Ciò renderebbe inutilizzabile il 'FileStream 'restituito. –

+0

Qual è il tipo di _zipFile? –

+2

Non hai davvero bisogno del file, salva invece in un flusso di memoria. –

risposta

4

si potrebbe creare una classe wrapper che implementa il contratto Stream e che contiene il FileStream internamente, oltre a mantenere il percorso del file.

Tutti i metodi e le proprietà standard Stream devono essere passati all'istanza FileStream.

Quando questa classe wrapper è Dispose d, è necessario (dopo Dispose nell'avvolto FileStream) quindi eliminare il file.

+0

Questo è quello che ho finito per fare. Ho appena esteso FileStream e nel costruttore passato nei percorsi temporanei di file/directory e nel metodo Dispose ho aggiunto il codice di cancellazione per il file temporaneo e la directory. Grazie per il suggerimento! – Bumper

+1

@Bumber, puoi pubblicare il tuo codice? – RayLoveless

2

Il problema è che è possibile eliminare il file solo dopo che è stato scritto nella risposta e il file viene scritto da FileStreamResult solo dopo che è stato restituito dall'azione.

Un modo per gestire è creare una sottoclasse di FileResult che eliminerà il file.

È più semplice creare una sottoclasse di FilePathResult in modo che la classe abbia accesso al nome file.

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

Nota: non ho provato quanto sopra. Rimuovi tutti i suoi bug prima di usarlo.

Ora modificare il codice del controller a qualcosa di simile:

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

Ho usato il consiglio di Damien_The_Unbeliever (risposta accettata), l'ho scritto e ha funzionato magnificamente. Ho pensato di condividere la classe:

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

Ecco una versione cutdown di quanto sopra che uso:

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

Io uso il metodo suggerito da @hwiechers, ma l'unico modo per farlo il lavoro è quello di chiudere il flusso di risposta prima di eliminare il file.

Ecco il codice sorgente, nota che scarico lo stream prima di eliminarlo.

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

} 

Ed ecco come il controller dovrebbe chiamarlo:

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