FileStreamResult - 因为正在被另一个进程使用,所以无法访问该文件。

3

ASP .NET Core

MVC控制器 - 使用FileStream从服务器存储下载文件,并返回FileStreamResult

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

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

一切正常,但是如果用户在下载完成之前取消了下载,那么控制器中与该文件相关的其他操作 (删除文件、重命名文件) 将无法工作,因为:进程无法访问该文件,因为它正在被另一个进程使用

当文件下载完成时,FileStream会自动释放,但由于某些原因,当用户手动终止下载时,它并不会终止。

我不得不重新启动Web应用程序 => 使用该文件的程序是IISExpress

请问是否有人知道如何在用户手动结束下载时释放流

编辑:

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

我试图在返回FileStreamResult后结束流,但我知道这是行不通的,因为在return File (stream, contentType, fileName)之后它立即跳转到finally块并且流被关闭,所以下载不会开始。


2
你应该将文件流放在 using 块内。 - Anand Sowmithiran
1
这里的“挑战”在于你正在返回一个流。似乎没有办法取消请求。有没有特定的原因?你不能像 return File(System.IO.File.ReadAllBytes(path), "... 这样做吗?或者更好的是,使用 CancellationToken 操作参数,并将其传递给 var bytes = await File.ReadAllBytesAsync(path, token); - JHBonarius
@JHBonarius,根据您的建议,使用File.ReadAllBytes(path)存在问题,即无法下载大小> 2GB的文件,这就是我使用Stream的原因。 - Foro
3
尊敬的上面的评论者(Anand,Ralf),请阅读文档Controller.File的构造函数接受一个Stream,将生成一个FileStreamResult。如果您不知道自己在说什么,请不要建议任何东西。 - JHBonarius
1
对于这个问题,关键在于如果你过早地处理它,它就不会工作;如果你不处理它,它也不会工作。也许可以将其存储在 using 内的不同对象中,或者当取消操作时抛出一些异常(例如 OperationCancelledException),你需要尝试一些方法。不幸的是,我没有办法测试你的代码,所以很难给出具体的解决方案。从我所见,我认为你可以做到,同时进行一些研究也不会有坏处。我确信某个地方的某个人也面临类似的问题,虽然我无法提供确定的解决方案,但我会给你 10 分的好问题奖励... - anastaciu
显示剩余5条评论
1个回答

4

看起来 FileStreamResult 类的源代码没有支持取消操作的功能。如果有需要,你需要自己实现。例如(未经测试,仅为想象):

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

您可以像这样使用它
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;
}

再次说明,这不是经过测试的,仅仅是想象。

编辑: 上面的回答针对的是ASP.net框架。而ASP.net core有一个完全不同的基础框架:在.Net Core中,该操作由执行器处理,如源代码所示。最终会调用FileResultHelper中的WriteFileAsync方法。在那里,可以看到使用context.RequestAborted参数来调用StreamCopyOperation。即在.Net Core中进行了取消操作。

重要问题是:为什么你的情况下请求没有被取消。


我认为这是一个很好的答案,谢谢你,但问题是我的问题与 .net core 相关,因此 FileResult 类不包含 WriteFile(HttpResponseBase response) - Foro
@Foro:我在源代码中进行了一些研究,发现.NET Core已经实现了取消操作。请查看我的更新/编辑。奇怪的是,在你的情况下它没有被触发。它应该可以工作的。 - JHBonarius
我明白,如果是这种情况,问题可能起源于其他地方。即使它没有解决我的问题,我也接受这个答案作为我的问题的正确答案。看起来你的解决方案确实应该确保流结束。 - Foro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接