Is there a clean way to return a FilePathResult and delete the on-disk file afterwards?
asp.net, asp.net-mvc, c#
Solution
The OnResultExecuted method runs after the response is written. You can override that with an ActionFilterAttribute.
public class DeleteFileAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Flush();
string filePath = (filterContext.Result as FilePathResult).FileName;
System.IO.File.Delete(filePath);
}
}
Just decorate your method as follows:
[DeleteFile]
[HttpPost, Route("/download")]
public ActionResult Download(InstallParams params)
{
using (SetupFile file = Generator.Generate(params))
{
Signer.Sign(file.LocalFilePath);
return new FilePathResult(file.LocalFilePath, "application/octet-stream")
{
FileDownloadName = file.DownloadFilename
};
}
}
Problem
I have a class that uses an external process to create a file and that gets returned as a download result, at which point I want to delete the file from the server. I generally like to avoid temporary on-disk files, but in this case it's impossible to avoid. I initially tried to implement this using a Dispose method: ``` public class SetupFile : IDisposable { /// <summary>Local file path</summary> public string LocalFilePath { get; set; } /// <summary>Filename to present to user</summary> public string DownloadFilename { get; set; } public void Dispose() { System.IO.File.Delete(LocalFile); } } ``` The controller code creates and operates on this file and then returns the result as a `FilePathResult`: ``` public class DownloadController : Controller { public SetupFileGenerator Generator { get; set; } public DigitalSignatureTool Signer { get; set; } [HttpPost, Route("/download")] public ActionResult Download(InstallParams params) { using (SetupFile file = Generator.Generate(params)) { { Signer.Sign(file.LocalFilePath); // note: requires a local path! return new FilePathResult(file.LocalFilePath, "application/octet-stream") { FileDownloadName = file.DownloadFilename }; } } } ``` (Note that `Generator` and `Signer` are being inserted to the Controller via Dependency Injection, and to maintain separation of concerns I didn't want `SetupFileGenerator` to depend on `DigitalSignatureTool`. Important bit about this is that I do need the file on-disk for `Signer.Sign` to run -- hence `Generator.Generate()` can't just return a Stream). The problem here is that `FilePathResult` only sends the file when its `WriteFile()` method gets called later in the processing pipeline, which means my `SetupFile.Dispose()` method has already been called. I think my next step would be to do one of two things: - Implement a new class that derives from `FilePathResult` but also deletes the file after it's sent - Refactor my code so that instead of `SetupFile` having a property `string LocalFilePath`, it has `MemoryStream FileContents` However, this seems like it would be a fairly common pattern, so before I go reinventing the wheel, is there a best practice for implementation? Anything to specifically watch out for?