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

基于C#和Angular的SignalR实时聊天流功能实现故障排查

问题解决:SignalR连接失败+聊天流实现方案

一、先排查SignalR连接失败问题

以下是常见问题点及修复方式:

  1. 后端Hub路由未配置
    在Program.cs(或Startup.cs)中必须添加Hub的路由映射:

    app.MapHub<MessageHub>("/MessageHub");
    

    确保这段代码放在app.UseRouting()之后、app.Run()之前。

  2. 跨域(CORS)配置缺失
    若前端和后端域名/端口不同,需配置允许SignalR的跨域请求:

    builder.Services.AddCors(options =>
    {
        options.AddPolicy("AllowAngular", policy =>
        {
            policy.WithOrigins("http://localhost:4200") // 替换为你的前端地址
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials() // SignalR必须配置此项
                  .WithExposedHeaders("Content-Disposition");
        });
    });
    
    // 启用CORS
    app.UseCors("AllowAngular");
    
  3. 方法名大小写不匹配
    SignalR默认会将C#的PascalCase方法名转换为camelCase,前端调用时需用小写开头的方法名:
    把原代码中的invoke('SendMessage')改为invoke('sendMessage')。

  4. 确保连接建立后再调用方法
    sendMessage中调用invoke时,可能连接还未完全建立,可通过检查连接状态或异步等待连接完成:

    async sendMessage(){
        // ...其他代码
        if (this._hubConnection.state !== HubConnectionState.Connected) {
            await this._hubConnection.start();
        }
        await this._hubConnection.invoke('sendMessage', this.userMessage, this.requestId);
        // ...其他代码
    }
    

二、实现聊天流(分片实时输出)

当前代码是一次性返回完整响应,需修改为逐段生成并推送:

后端修改(C#)

将Hub方法改为异步流,每生成一段响应就推送给当前用户:

// 假设_chatbotProvider.GetResponse改为返回分片的异步流
public async IAsyncEnumerable<string> SendMessage(string message, int requestId)
{
    // 替换为你实际的文本分片生成逻辑
    foreach (var chunk in _chatbotProvider.GetResponseChunks(message, requestId))
    {
        // 只推送给当前发起请求的客户端,而非所有用户
        await Clients.Caller.SendAsync("ReceiveMessageChunk", chunk);
        // 可选:添加延迟模拟生成过程,让流效果更直观
        await Task.Delay(50);
    }
}

前端修改(Angular)

  • 提前绑定接收事件,避免重复绑定
  • 实时追加分片内容到同一条助手消息中:
import { ChangeDetectorRef } from '@angular/core';
import { HubConnection, HubConnectionBuilder, HubConnectionState } from '@microsoft/signalr';

// ...组件代码
constructor(private cdr: ChangeDetectorRef) {}

ngOnInit(): void {
    this._hubConnection = new HubConnectionBuilder()
        .withUrl("/MessageHub", { withCredentials: true }) // 跨域时需添加
        .build();

    // 绑定分片接收事件,实时更新聊天内容
    this._hubConnection.on("ReceiveMessageChunk", (chunk) => {
        // 找到最后一条助手消息,追加分片内容
        const lastAssistantMsg = this.chatMessages.findLast(msg => msg.role === 'assistant');
        if (lastAssistantMsg) {
            lastAssistantMsg.content += chunk;
        } else {
            // 首次收到分片,创建新的助手消息
            this.chatMessages.push({ role: 'assistant', content: chunk });
        }
        // 手动触发变更检测,确保页面实时更新
        this.cdr.detectChanges();
    });

    this._hubConnection.start()
        .then(() => console.log('Connection Started!'))
        .catch(err => console.error('Connection error:', err));
}

async sendMessage(){
    this.requestId = this.route.snapshot.params['requestId'];

    // 添加用户消息到列表
    this.chatMessages.push({ role: 'user', content: this.userMessage });

    // 确保连接已建立
    if (this._hubConnection.state !== HubConnectionState.Connected) {
        await this._hubConnection.start();
    }

    try {
        await this._hubConnection.invoke('sendMessage', this.userMessage, this.requestId);
    } catch (err) {
        console.error('Invoke error:', err);
    }

    this.userMessage = '';
}

三、替代方案:Server-Sent Events(SSE)

如果不需要双向通信(仅后端向前端推流),SSE是更轻量的选择:

后端控制器代码

[ApiController]
[Route("api/chat")]
public class ChatController : ControllerBase
{
    private readonly IChatbotProvider _chatbotProvider;

    public ChatController(IChatbotProvider chatbotProvider)
    {
        _chatbotProvider = chatbotProvider;
    }

    [HttpGet("stream")]
    public async Task ChatStream(string message, int requestId)
    {
        // 设置SSE响应头
        Response.ContentType = "text/event-stream";
        Response.Headers.CacheControl = "no-cache";
        Response.Headers.Connection = "keep-alive";

        // 逐段推送内容
        foreach (var chunk in _chatbotProvider.GetResponseChunks(message, requestId))
        {
            await Response.WriteAsync($"data: {chunk}\n\n");
            await Response.Body.FlushAsync();
            await Task.Delay(50);
        }
    }
}

前端代码

sendMessage(){
    this.requestId = this.route.snapshot.params['requestId'];
    this.chatMessages.push({ role: 'user', content: this.userMessage });

    // 创建SSE连接
    const eventSource = new EventSource(
        `/api/chat/stream?message=${encodeURIComponent(this.userMessage)}&requestId=${this.requestId}`
    );

    // 接收分片内容
    eventSource.onmessage = (event) => {
        const chunk = event.data;
        const lastAssistantMsg = this.chatMessages.findLast(msg => msg.role === 'assistant');
        if (lastAssistantMsg) {
            lastAssistantMsg.content += chunk;
        } else {
            this.chatMessages.push({ role: 'assistant', content: chunk });
        }
        this.cdr.detectChanges();
    };

    // 处理错误
    eventSource.onerror = (err) => {
        console.error('SSE error:', err);
        eventSource.close();
    };

    this.userMessage = '';
}

内容的提问来源于stack exchange,提问作者Rahul Borole

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:34:58