You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 12:45:16