如何使用 ASP.NET Core 进行流式传输
How to stream with ASP.NET Core
如何在 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报错:
The localhost page isn’t working
localhost unexpectedly closed the connection.
ERR_INCOMPLETE_CHUNKED_ENCODING
P.S。我要直播很多内容,所以不能提前指定Content-Lengthheader
类似这样的方法可能有效:
[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);
}
要流式传输应该像下载文件一样出现在浏览器中的响应,您应该使用 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"
};
}
我也想知道如何做到这一点,并且发现了
原始问题的代码实际上在 ASP.NET Core 2.1.0-rc1-final
上运行正常,Chrome(和其他少数浏览器)和 JavaScript 应用程序都不会因此类端点而失败。
我想添加的小东西只是设置 StatusCode 并关闭响应流以使响应完成:
[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!");
}
}
}
可以 return null
或 EmptyResult()
(等价),即使之前写入 Response.Body
。如果方法 returns 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;
}
这个问题有点老了,但我在任何地方都找不到更好的答案来解决我想做的事情。要将当前缓冲的输出发送到客户端,您必须为要写入的每个内容块调用 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
@Developer4993 是正确的,要在解析整个响应之前将数据发送到客户端,有必要 Flush
到响应流。然而,他们的回答对于 DELETE
和 Synchronized.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 流,其中包含您不想在发送前缓冲的大量内容,这将向响应写入一大块文本每个 FlushAsync()
流。
出于我的目的,使用 HttpClient
的结果比浏览器兼容性更重要,但如果您发送足够的文本,您将看到 chromium 浏览器以流方式使用结果。浏览器一开始好像缓冲了一定数量。
最新的 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)
扔进任一 foreach
以演示连续流。
最后,@StephenCleary 的 FileCallbackResult
与这两个示例的工作方式相同。 Infrastructure
命名空间深处的 FileResultExecutorBase
只是有点可怕。
[HttpGet]
public IActionResult Get()
{
return new FileCallbackResult(new MediaTypeHeaderValue("text/plain"), async (outputStream, _) =>
{
StreamWriter sw;
await using ((sw = new StreamWriter(outputStream)).ConfigureAwait(false))
{
foreach (var item in someReader.Read())
{
await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
await sw.FlushAsync().ConfigureAwait(false);
}
}
outputStream.Close();
});
}
如何在 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报错:
The localhost page isn’t working
localhost unexpectedly closed the connection.
ERR_INCOMPLETE_CHUNKED_ENCODING
P.S。我要直播很多内容,所以不能提前指定Content-Lengthheader
类似这样的方法可能有效:
[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);
}
要流式传输应该像下载文件一样出现在浏览器中的响应,您应该使用 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"
};
}
我也想知道如何做到这一点,并且发现了
原始问题的代码实际上在 ASP.NET Core 2.1.0-rc1-final
上运行正常,Chrome(和其他少数浏览器)和 JavaScript 应用程序都不会因此类端点而失败。
我想添加的小东西只是设置 StatusCode 并关闭响应流以使响应完成:
[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!");
}
}
}
可以 return null
或 EmptyResult()
(等价),即使之前写入 Response.Body
。如果方法 returns 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;
}
这个问题有点老了,但我在任何地方都找不到更好的答案来解决我想做的事情。要将当前缓冲的输出发送到客户端,您必须为要写入的每个内容块调用 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
@Developer4993 是正确的,要在解析整个响应之前将数据发送到客户端,有必要 Flush
到响应流。然而,他们的回答对于 DELETE
和 Synchronized.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 流,其中包含您不想在发送前缓冲的大量内容,这将向响应写入一大块文本每个 FlushAsync()
流。
出于我的目的,使用 HttpClient
的结果比浏览器兼容性更重要,但如果您发送足够的文本,您将看到 chromium 浏览器以流方式使用结果。浏览器一开始好像缓冲了一定数量。
最新的 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)
扔进任一 foreach
以演示连续流。
最后,@StephenCleary 的 FileCallbackResult
与这两个示例的工作方式相同。 Infrastructure
命名空间深处的 FileResultExecutorBase
只是有点可怕。
[HttpGet]
public IActionResult Get()
{
return new FileCallbackResult(new MediaTypeHeaderValue("text/plain"), async (outputStream, _) =>
{
StreamWriter sw;
await using ((sw = new StreamWriter(outputStream)).ConfigureAwait(false))
{
foreach (var item in someReader.Read())
{
await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
await sw.FlushAsync().ConfigureAwait(false);
}
}
outputStream.Close();
});
}