如何使用ASP.NET Core进行流式处理

时间:2017-03-13 18:58:45

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

如何正确地在ASP.NET Core中传输响应? 有这样的控制器(更新的代码):

[HttpGet("test")]
public async Task GetTest()
{
    HttpContext.Response.ContentType = "text/plain";
    using (var writer = new StreamWriter(HttpContext.Response.Body))
        await writer.WriteLineAsync("Hello World");            
}

Firefox / Edge浏览器显示

  

Hello World

,而Chrome / Postman报告错误:

  

localhost页面无效

     

localhost意外关闭了连接。

     

ERR_INCOMPLETE_CHUNKED_ENCODING

P.S。我即将流式传输大量内容,因此我无法提前指定Content-Length标头。

6 个答案:

答案 0 :(得分:16)

要将应该在浏览器中显示的响应(例如下载的文件)流式传输,您应该使用FileStreamResult

[HttpGet]
public FileStreamResult GetTest()
{
  var stream = new MemoryStream(Encoding.ASCII.GetBytes("Hello World"));
  return new FileStreamResult(stream, new MediaTypeHeaderValue("text/plain"))
  {
    FileDownloadName = "test.txt"
  };
}

答案 1 :(得分:2)

我也想知道如何做到这一点,并且发现了这一点 原始问题的代码在ASP.NET Core 2.1.0-rc1-final上确实可以正常运行,Chrome(以及其他一些浏览器)和JavaScript应用程序都不会因此类终端而失败。

我想添加的一些小问题只是设置StatusCode并关闭响应Stream以使响应得到满足:

[HttpGet("test")]
public void Test()
{
    Response.StatusCode = 200;
    Response.ContentType = "text/plain";
    using (Response.Body)
    {
        using (var sw = new StreamWriter(Response.Body))
        {
            sw.Write("Hi there!");
        }
    }
}

答案 2 :(得分:2)

即使先前写入null,也可能返回EmptyResult()Response.Body(等效)。如果该方法返回ActionResult以便能够轻松使用所有其他结果(例如BadQuery()),可能会很有用。

[HttpGet("test")]
public ActionResult Test()
{
    Response.StatusCode = 200;
    Response.ContentType = "text/plain";
    using (var sw = new StreamWriter(Response.Body))
    {
        sw.Write("something");
    }
    return null;
}

答案 3 :(得分:2)

@ Developer4993是正确的,要在解析完整个响应之前将数据发送到客户端,有必要Flush到响应流。但是,对于DELETESynchronized.StreamWriter来说,他们的回答都有点不合常规。此外,如果I / O是同步的,则Asp.Net Core 3.x将引发异常。 这已在Asp.Net Core 3.1中进行了测试:

[HttpGet]
public async Task Get()
{
    Response.ContentType = "text/plain";
    StreamWriter sw;
    await using ((sw = new StreamWriter(Response.Body)).ConfigureAwait(false))
    {
        foreach (var item in someReader.Read())
        {
            await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
            await sw.FlushAsync().ConfigureAwait(false);
        }
    }
}

假设someReader在发送前不想缓存的数据库结果或某些I / O流具有大量不想缓存的内容,这将在每个{ {1}}。 出于我的目的,使用FlushAsync()使用结果比浏览器兼容性更重要,但是如果发送足够的文本,则会看到Chrome浏览器以流方式使用结果。浏览器似乎一开始会缓冲一定数量。

对于最新的HttpClient流来说,这变得更有用的地方,您的源要么是时间密集型的,要么是磁盘密集型的,但有时却会产生一点点:

IAsyncEnumerable

您可以将[HttpGet] public async Task<EmptyResult> Get() { Response.ContentType = "text/plain"; StreamWriter sw; await using ((sw = new StreamWriter(Response.Body)).ConfigureAwait(false)) { await foreach (var item in GetAsyncEnumerable()) { await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false); await sw.FlushAsync().ConfigureAwait(false); } } return new EmptyResult(); } 放到await Task.Delay(1000)中以演示连续流。

最后,@StephenCleary的foreach的工作原理也与这两个示例相同。从FileCallbackResult名称空间的深处开始,FileResultExecutorBase有点令人恐惧。

Infrastructure

答案 4 :(得分:0)

这个问题有点老了,但是对于我想做的事情,我找不到任何更好的答案。要将当前缓冲的输出发送到客户端,必须为要编写的每个内容块调用Flush()。只需执行以下操作:

[HttpDelete]
public void Content()
{
    Response.StatusCode = 200;
    Response.ContentType = "text/html";

    // the easiest way to implement a streaming response, is to simply flush the stream after every write.
    // If you are writing to the stream asynchronously, you will want to use a Synchronized StreamWriter.
    using (var sw = StreamWriter.Synchronized(new StreamWriter(Response.Body)))
    {
        foreach (var item in new int[] { 1, 2, 3, 4, })
        {
            Thread.Sleep(1000);
            sw.Write($"<p>Hi there {item}!</p>");
            sw.Flush();
        }
    };
}

您可以使用以下命令进行curl测试:curl -NX DELETE <CONTROLLER_ROUTE>/content

答案 5 :(得分:-1)

这样的事情可能有用:

[HttpGet]
public async Task<IActionResult> GetTest()
{
    var contentType = "text/plain";
    using (var stream = new MemoryStream(Encoding.ASCII.GetBytes("Hello World")))
    return new FileStreamResult(stream, contentType);

}