ASP.NET Core Web API返回流及Angular端实时读取方案咨询
最佳实现方案
后端(ASP.NET Core)调整
方案1:用IAsyncEnumerable实现分块流式响应
原代码的async IEnumerable<string>需改为async IAsyncEnumerable<string>——ASP.NET Core对IAsyncEnumerable原生支持流式传输,会自动启用分块编码(Transfer-Encoding: chunked),逐个发送yield返回的内容。
修改后的控制器方法:
[HttpPost] // 根据实际场景选择Get/Post,建议用Post传递Info[]参数 public async IAsyncEnumerable<string> Execute([FromBody] Info[] infoArray) { foreach (var info in infoArray) { try { var result = await ProcessInfo(info); // 每个结果后加换行,方便前端拆分内容 yield return result + "\n"; } catch (ProcessException e) { yield return $"错误:{e.Message}\n"; } // 可选:加微小延迟避免后端合并发送,确保前端能逐个接收 await Task.Delay(10); } }
方案2:用Server-Sent Events(SSE)适配实时场景
SSE是专为服务器向客户端推送实时数据设计的协议,前端处理更简洁,无需手动拆分响应内容。
后端返回ContentResult,设置专属的text/event-stream类型,按SSE格式发送事件:
[HttpPost] public async Task<IActionResult> Execute([FromBody] Info[] infoArray) { var response = Response; response.Headers.Add("Content-Type", "text/event-stream"); response.Headers.Add("Cache-Control", "no-cache"); response.Headers.Add("Connection", "keep-alive"); var writer = new StreamWriter(response.Body); foreach (var info in infoArray) { try { var result = await ProcessInfo(info); // SSE标准格式:data: 内容\n\n await writer.WriteLineAsync($"data: {result}"); await writer.WriteLineAsync(); await writer.FlushAsync(); } catch (ProcessException e) { await writer.WriteLineAsync($"data: 错误:{e.Message}"); await writer.WriteLineAsync(); await writer.FlushAsync(); } // 定期发送心跳避免客户端超时 await Task.Delay(10); } // 发送执行结束标记 await writer.WriteLineAsync("data: [执行完成]"); await writer.WriteLineAsync(); await writer.FlushAsync(); return new EmptyResult(); }
前端(TypeScript)实现
方案1:适配分块text/plain的Axios实现
Axios浏览器端默认不直接支持流式接收,可通过onDownloadProgress事件读取增量内容:
import axios from 'axios'; // 记录已接收的文本,避免重复处理 let receivedText = ''; async function executeProcess(infoArray: Info[]) { const response = await axios.post('/api/your-controller/execute', infoArray, { responseType: 'text', onDownloadProgress: (progressEvent) => { const newText = progressEvent.target.responseText.substring(receivedText.length); if (newText) { // 按换行拆分单个结果 const lines = newText.split('\n').filter(line => line.trim() !== ''); lines.forEach(line => { // 此处替换为更新GUI日志的逻辑 console.log(line); // 示例:document.getElementById('log-panel').textContent += line + '\n'; }); receivedText = progressEvent.target.responseText; } } }); // 处理最后剩余的内容 const remainingText = response.data.substring(receivedText.length); if (remainingText) { const lines = remainingText.split('\n').filter(line => line.trim() !== ''); lines.forEach(line => console.log(line)); } }
方案2:SSE的原生Fetch实现(更简洁)
用Fetch结合ReadableStream处理POST请求的SSE响应,无需Axios:
function executeProcess(infoArray: Info[]) { fetch('/api/your-controller/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(infoArray) }).then(response => { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; function readChunk() { reader.read().then(({ done, value }) => { if (done) { console.log('流程执行完毕'); return; } buffer += decoder.decode(value, { stream: true }); // 按SSE的\n\n分隔符拆分事件 const events = buffer.split('\n\n'); buffer = events.pop() || ''; events.forEach(event => { if (event.startsWith('data: ')) { const message = event.substring(6).trim(); // 更新GUI日志 console.log(message); // 示例:document.getElementById('log-panel').textContent += message + '\n'; } }); readChunk(); }); } readChunk(); }).catch(err => console.error('请求失败:', err)); }
关键注意事项
- 禁用响应缓冲:ASP.NET Core中
IAsyncEnumerable默认禁用缓冲,自定义流写入时需确保Response.Body未被缓冲(默认状态即可)。 - 跨域配置:若前后端跨域,后端CORS需允许
text/event-stream类型及Connection: keep-alive等请求头。 - 超时处理:长时间运行的请求需定期发送心跳(如SSE的空
data事件),避免浏览器触发超时。
内容的提问来源于stack exchange,提问作者Fraser Orr
相关产品推荐
相关产品推荐

