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
CodePudding user response:
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.