FileStreamResult - The process cannot access the file, because it is being used by another process

2.3k views Asked by At

ASP .NET Core

MVC Controller - download file from server storage using FileStream and returning FileStreamResult

public IActionResult Download(string path, string fileName)
{
    var fileStream = System.IO.File.OpenRead(path);

    return File(fileStream, "application/force-download", fileName);
}

Everything works fine, but once the user cancels downloading before the download is complete, other actions in the controller working with this file (Delete file, rename file) do not work because: The process cannot access the file, because it is being used by another process

FileStream automatically dispose when the file download is complete, but for some reason it does not terminate when the user terminates the download manually.

I have to restart the web application => the program that uses the file is IISExpress

Does anyone please know how to dispose stream if the user manually ends the download?

EDIT:

FileStream stream = null;
try
{
    using (stream = System.IO.File.OpenRead(path))
    {
        return File(stream, "application/force-download", fileName);
    }
}

Code that I tried to end the Stream after returning FileStreamResult, I am aware that it can not work, because after return File (stream, contentType, fileName) it immediately jumps to the block finally and the stream closes, so the download does not start because the stream is closed

1

There are 1 answers

3
JHBonarius On BEST ANSWER

It seems the source of the FileStreamResult class shows it has no support for cancellation. You will need to implement your own, if required. E.g. (not-tested, just imagined)

using System.IO;

namespace System.Web.Mvc
{
    public class CancellableFileStreamResult : FileResult
    {
        // default buffer size as defined in BufferedStream type
        private const int BufferSize = 0x1000;

        private readonly CancellationToken _cancellationToken;

        public CancellableFileStreamResult(Stream fileStream, string contentType,
            CancellationToken cancellationToken)
            : base(contentType)
        {
            if (fileStream == null)
            {
                throw new ArgumentNullException("fileStream");
            }

            FileStream = fileStream;
            _cancellationToken = cancellationToken;
        }

        public Stream FileStream { get; private set; }

        protected override void WriteFile(HttpResponseBase response)
        {
            // grab chunks of data and write to the output stream
            Stream outputStream = response.OutputStream;
            using (FileStream)
            {
                byte[] buffer = new byte[BufferSize];

                while (!_cancellationToken.IsCancellationRequested)
                {
                    int bytesRead = FileStream.Read(buffer, 0, BufferSize);
                    if (bytesRead == 0)
                    {
                        // no more data
                        break;
                    }

                    outputStream.Write(buffer, 0, bytesRead);
                }
            }
        }
    }
}

You can then use it like

public IActionResult Download(string path, string fileName, CancellationToken cancellationToken)
{
    var fileStream = System.IO.File.OpenRead(path);
    var result = new CancellableFileStreamResult(
        fileStream, "application/force-download", cancellationToken);
    result.FileDownloadName = fileName;
    return result;
}

Again, I'm this is not tested, just imagined. Maybe this doesn't work, as the action is already finished, thus cannot be cancelled anymore.

EDIT: The above answer "Imagined" for ASP.net framework. ASP.net core has a quite different underlying framework: In .net core, the action is processed by and executor, as shown in the source. That will eventually call WriteFileAsync in the FileResultHelper. There you can see that StreamCopyOperation is called with the cancellationToken context.RequestAborted. I.e. cancellation is in place in .net Core.

The big question is: why isn't the request aborted in your case.