【问题标题】:Streaming an in-memory generated file in ASP.NET Core在 ASP.NET Core 中流式传输内存中生成的文件
【发布时间】:2019-10-20 20:15:13
【问题描述】:

在互联网上搜索了几个小时后,我不知道如何解决我的 ASP.NET Core 2.x 问题。

我正在动态生成 CSV(可能需要几分钟),然后尝试将其发送回客户端。在我开始发送响应之前,许多客户端都超时了,所以我试图将文件流回给他们(立即响应 200)并异步写入流。以前在 ASP 中使用 PushStreamContent 似乎可以做到这一点,但我不确定如何构造我的代码,以便异步完成 CSV 生成并立即返回 HTTP 响应。

[HttpGet("csv")]
public async Task<FileStreamResult> GetCSV(long id)
{
    // this stage can take 2+ mins, which obviously blocks the response
    var data = await GetData(id);
    var records = _csvGenerator.GenerateRecords(data); 

    // using the CsvHelper Nuget package
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    var csv = new CsvWriter(writer);

    csv.WriteRecords(stream, records);
    await writer.FlushAsync();

    return new FileStreamResult(stream, new MediaTypeHeaderValue("text/csv))
    {
        FileDownloadName = "results.csv"
    };
 }

如果您向此控制器方法发出请求,则在整个 CSV 完成生成并最终获得响应之前,您将一无所获,此时大多数客户端请求已超时。

我尝试将 CSV 生成代码包装在 Task.Run() 中,但这也对我的问题没有帮助。

【问题讨论】:

  • 您可以使用Response.Body 直接写入输出流而不是MemoryStream。不过,这对await GetData(); 没有任何作用。这与流式传输或 CSV 生成无关。
  • 这很公平,但是您能建议一种方法来重组代码以实现这一点吗?旧的 PushStreamContent 类用于让您注册一个回调,该回调将在初始 200 响应后执行。
  • 要写入流,只需使用Response.Body,例如new StreamWriter(Response.Body)。但是,这不会使慢查询运行得更快,并且您仍然会超时。 GetData 做了什么,为什么需要 2 分钟?修复错误查询可能比后台生成 CSV 文件更有效(也更快)。
  • 谢谢,我会试试的。我正在关注本教程,该教程展示了如何立即响应 200,然后写入输出流:blog.stephencleary.com/2016/10/async-pushstreamcontent.html
  • 它加载了多少数据?如果加载 100 行需要 2 分钟,那么编写它的人应该修复它。

标签: c# asp.net-mvc asp.net-core .net-core csvhelper


【解决方案1】:

ASP.NET Core 没有内置 PushStreamContext 类型的类型。但是,您可以使用 build your own FileCallbackResult 来做同样的事情。这个example code 应该这样做:

public class FileCallbackResult : FileResult
{
    private Func<Stream, ActionContext, Task> _callback;

    public FileCallbackResult(MediaTypeHeaderValue contentType, Func<Stream, ActionContext, Task> callback)
        : base(contentType?.ToString())
    {
        if (callback == null)
            throw new ArgumentNullException(nameof(callback));
        _callback = callback;
    }

    public override Task ExecuteResultAsync(ActionContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        var executor = new FileCallbackResultExecutor(context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>());
        return executor.ExecuteAsync(context, this);
    }

    private sealed class FileCallbackResultExecutor : FileResultExecutorBase
    {
        public FileCallbackResultExecutor(ILoggerFactory loggerFactory)
            : base(CreateLogger<FileCallbackResultExecutor>(loggerFactory))
        {
        }

        public Task ExecuteAsync(ActionContext context, FileCallbackResult result)
        {
            SetHeadersAndLog(context, result, null);
            return result._callback(context.HttpContext.Response.Body, context);
        }
    }
}

用法:

[HttpGet("csv")]
public IActionResult GetCSV(long id)
{
  return new FileCallbackResult(new MediaTypeHeaderValue("text/csv"), async (outputStream, _) =>
  {
    var data = await GetData(id);
    var records = _csvGenerator.GenerateRecords(data); 
    var writer = new StreamWriter(outputStream);
    var csv = new CsvWriter(writer);
    csv.WriteRecords(stream, records);
    await writer.FlushAsync();
  })
  {
    FileDownloadName = "results.csv"
  };
}

请记住,FileCallbackResultPushStreamContext 具有相同的限制:如果在回调中发生错误,则 Web 服务器无法通知客户端该错误。您所能做的就是传播异常,这将导致 ASP.NET 提前关闭连接,因此客户端会收到“连接意外关闭”或“下载中止”错误。这是因为 HTTP 在正文开始流式传输之前在标头中发送错误代码 first

【讨论】:

  • 非常感谢斯蒂芬。这是您在 PushStreamContent 上的原始教程,我的解决方案正是基于此!
  • 感谢您的示例,但似乎至少应将一个字节发送到浏览器以显示 SaveFile 对话框。
【解决方案2】:

如果文档生成需要2+ 分钟,则它应该是asynchronous。可能是这样的:

  1. 客户端发送请求生成文档
  2. 您接受请求,在后台开始生成并回复类似generation has been started, we will notify you 的消息
  3. 在客户端您定期检查文档是否准备好并最终获取链接

您也可以使用signalr 进行操作。步骤相同,但客户不需要检查文档的状态。完成后可以推送链接。

【讨论】:

  • 谢谢,我想你可能是对的,生成应该是异步的。只是在将您的答案标记为正确之前将问题留待更长时间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-08
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 2014-02-09
相关资源
最近更新 更多