Mariusz Jamro
Mariusz Jamro

Reputation: 31643

How to delete a file after it was streamed in ASP.NET Core

I would like to delete a temporary file after returning it form action. How can i achieve that with ASP.NET Core:

public IActionResult Download(long id)
{
    var file = "C:/temp/tempfile.zip";
    var fileName = "file.zip;
    return this.PhysicalFile(file, "application/zip", fileName);
    // I Would like to have File.Delete(file) here !!
}

The file is too big for returning using memory stream.

Upvotes: 13

Views: 8332

Answers (5)

Heiner
Heiner

Reputation: 2056

You can create a FileStream that deletes the underlying file as soon as the stream is closed. With the option DeleteOnClose. As the stream is closed automatically after download, the file is deleted.

var fileStream = new FileStream(
    Path.GetTempFileName(),
    FileMode.Create, 
    FileAccess.ReadWrite, 
    FileShare.Read, 
    4096, 
    FileOptions.DeleteOnClose);

// add something to the file

fileStream.Position = 0;

return File(fileStream, MediaTypeNames.Application.Octet);

Upvotes: 10

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131305

File() or PhysicalFile() return a FileResult-derived class that just delegates processing to an executor service. PhysicalFileResult's ExecuteResultAsync method calls :

var executor = context.HttpContext.RequestServices
                 .GetRequiredService<IActionResultExecutor<PhysicalFileResult>>();
return executor.ExecuteAsync(context, this);

All other FileResult-based classes work in a similar way.

The PhysicalFileResultExecutor class essentially writes the file's contents to the Response stream.

A quick and dirty solution would be to create your own PhysicalFileResult-based class that delegates to PhysicalFileResultExecutor but deletes the file once the executor finishes :

public class TempPhysicalFileResult : PhysicalFileResult
{
    public TempPhysicalFileResult(string fileName, string contentType)
                 : base(fileName, contentType) { }
    public TempPhysicalFileResult(string fileName, MediaTypeHeaderValue contentType)
                 : base(fileName, contentType) { }

    public override async  Task ExecuteResultAsync(ActionContext context)
    {
        try {
            await base.ExecuteResultAsync(context);
        }
        finally {
            File.Delete(FileName);
        }
    }
}

Instead of calling PhysicalFile() to create the PhysicalFileResult you can create and return a TempPhysicalFileResult, eg :

return new TempPhysicalFileResult(file, "application/zip"){FileDownloadName=fileName};

That's the same thing PhysicalFile() does :

[NonAction]
public virtual PhysicalFileResult PhysicalFile(
    string physicalPath,
    string contentType,
    string fileDownloadName)
    => new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName };

A more sophisticated solution would be to create a custom executor that took care eg of compression as well as cleaning up files, leaving the action code clean of result formatting code

Upvotes: 19

A_kat
A_kat

Reputation: 1527

Since the file is big one solution is to send a token (like a guid) along side the request. Only once the download has been completed you want to delete the file (otherwise you risk losing it). So using that token the client will make a new request using the aforementioned token. So you will know that the download was successful and that you can proceed to delete the file.

I suggest using a Dictionary to map a token to a file.

Deleting on one request seems like a bad practice due to the fact that you can easily lose the file.

Also you can use Henk Mollema answer to be certain that all files are cleared periodically if that is what you are trying to achieve.

Upvotes: 1

Arjun Vachhani
Arjun Vachhani

Reputation: 1958

You can load content of file. delete file and send content in response. in MVC you can return a file in response. check the snippet below

public FileResult Download()
{
    var fileName = $"client-file-name.ext";
    var filepath = $"filepath";
    byte[] fileBytes = System.IO.File.ReadAllBytes(filepath);
    //delete file here
    return File(fileBytes, "application/x-msdownload", fileName);
}

Upvotes: -1

Henk Mollema
Henk Mollema

Reputation: 46531

It would be hard to do this within the same request as the requests ends directly after the streaming is complete. A better option might be to run a timed background task via a hosted service which periodically removes all files (e.g. with a last write time of >5 minutes ago) from the temp directory.

Upvotes: 0

Related Questions