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

ASP.NET Core 8+Angular 18 SignalR SQL操作通知未推送问题排查

问题:ASP.NET Core SignalR无法推送CRUD通知到Angular客户端

我正在开发一个项目,技术栈为ASP.NET Core 8 + SignalR + Angular 18 + SQL Server 2022 Express,目标是实现数据库CRUD操作后的实时通知。但执行CRUD后,Angular客户端接收不到SignalR推送的通知,Postman连接Hub后也收不到通知,直接访问Hub端点会返回“Connection ID required”错误。

项目配置

Program.cs

using DocumentApp.Models;
using DocumentApp.Hubs;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddSignalR();
builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAngularApp",
        builder => builder
            .WithOrigins("http://localhost:4200")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials());
});

var app = builder.Build();

app.UseCors("AllowAngularApp");
app.UseHttpsRedirection();
app.UseAuthorization();

app.MapControllers();
app.MapHub<NotificationHub>("/NotificationHub");

app.Run();

AppDbContext.cs

using Microsoft.EntityFrameworkCore;

namespace DocumentApp.Models
{
    public class AppDbContext : DbContext
    {
        public DbSet<Document> Documents { get; set; }

        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Document>().HasKey(d => d.DocumentId);
        }
    }

    public class Document
    {
        public int DocumentId { get; set; }
        public string DocumentCode { get; set; } = string.Empty;
    }
}

NotificationHub.cs

using Microsoft.AspNetCore.SignalR;

namespace DocumentApp.Hubs
{
    public class NotificationHub : Hub
    {
        // This hub will be used to send notifications to clients
    }
}

DocumentsController.cs

using DocumentApp.Models;
using DocumentApp.Hubs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;

namespace DocumentApp.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class DocumentsController : ControllerBase
    {
        private readonly AppDbContext _context;
        private readonly IHubContext<NotificationHub> _hubContext;

        public DocumentsController(AppDbContext context, IHubContext<NotificationHub> hubContext)
        {
            _context = context;
            _hubContext = hubContext;
        }

        [HttpGet]
        public async Task<ActionResult<IEnumerable<Document>>> GetDocuments()
        {
            await _hubContext.Clients.All.SendAsync("ReceiveUpdate", "Documents retrieved!");
            return await _context.Documents.ToListAsync();
        }

        [HttpPost]
        public async Task<IActionResult> CreateDocument([FromBody] Document document)
        {
            _context.Documents.Add(document);
            await _context.SaveChangesAsync();
            await _hubContext.Clients.All.SendAsync("ReceiveUpdate", $"Document {document.DocumentCode} created!");
            return Ok(document);
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateDocument(int id, [FromBody] Document document)
        {
            var existingDocument = await _context.Documents.FindAsync(id);
            if (existingDocument == null) return NotFound();

            existingDocument.DocumentCode = document.DocumentCode;
            await _context.SaveChangesAsync();
            await _hubContext.Clients.All.SendAsync("ReceiveUpdate", $"Document {document.DocumentCode} updated!");
            return Ok(existingDocument);
        }
    }
}

Angular 18 SignalRService (signalr.service.ts)

import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr';

@Injectable({
  providedIn: 'root'
})
export class SignalRService {
  private hubConnection: signalR.HubConnection;

  constructor() {
    this.hubConnection = new signalR.HubConnectionBuilder()
      .withUrl('https://localhost:7203/NotificationHub')
      .configureLogging(signalR.LogLevel.Information)
      .build();

    this.hubConnection.start()
      .then(() => console.log('SignalR connection established'))
      .catch(err => console.error('Error establishing connection:', err));

    this.hubConnection.on('ReceiveUpdate', (message: string) => {
      console.log('Notification received:', message);
    });
  }
}

SQL Server配置

ALTER DATABASE DMS_DB_1_0_0
SET ENABLE_BROKER;
GO

已启用SQL Server Agent。

问题现象

  • CRUD操作执行后,Angular客户端无通知推送
  • Postman可成功连接/NotificationHub,但同样收不到通知
  • 浏览器直接访问https://localhost:7203/NotificationHub返回“Connection ID required”错误

已排查步骤

  • 确认Program.cs中Hub映射路径正确
  • 检查Angular客户端SignalR配置无明显错误
  • 验证CORS策略允许Angular应用(http://localhost:4200)连接
  • Postman测试WebSocket连接成功,但无通知接收

解决方案与调试技巧

1. 检查SignalR连接的URL一致性

  • 查看ASP.NET Core启动日志,确认后端实际监听的端口和协议,避免前后端URL不匹配(比如后端用HTTP而前端用HTTPS,或者端口错误)
  • 若Angular使用HTTP(http://localhost:4200),后端用HTTPS,会存在混合内容问题,建议统一协议,或在Angular开发环境临时允许混合内容

2. 启用详细日志排查连接问题

  • 后端添加SignalR日志配置:
    builder.Logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug);
    builder.Logging.AddFilter("Microsoft.AspNetCore.Http.Connections", LogLevel.Debug);
    
    查看日志中是否有连接建立、消息发送的记录,确认消息是否真的被发送
  • Angular端将日志级别改为LogLevel.Trace,查看连接详细过程:
    .configureLogging(signalR.LogLevel.Trace)
    

3. 确保SignalR连接稳定(添加重连逻辑)

当前Angular代码仅启动一次连接,断开后不会自动重连,添加重连逻辑:

constructor() {
  this.hubConnection = new signalR.HubConnectionBuilder()
    .withUrl('https://localhost:7203/NotificationHub')
    .configureLogging(signalR.LogLevel.Trace)
    .withAutomaticReconnect() // 启用自动重连
    .build();

  this.startConnection();

  this.hubConnection.on('ReceiveUpdate', (message: string) => {
    console.log('Notification received:', message);
  });
}

private startConnection() {
  this.hubConnection.start()
    .then(() => console.log('SignalR connection established'))
    .catch(err => {
      console.error('Error establishing connection:', err);
      setTimeout(() => this.startConnection(), 5000); // 5秒后重试
    });
}

4. 验证IHubContext注入与消息发送

在DocumentsController中添加日志,确认_hubContext不为null且消息发送无异常:

[HttpPost]
public async Task<IActionResult> CreateDocument([FromBody] Document document)
{
    _context.Documents.Add(document);
    await _context.SaveChangesAsync();
    try
    {
        if (_hubContext == null)
        {
            Console.WriteLine("_hubContext is null!");
        }
        else
        {
            await _hubContext.Clients.All.SendAsync("ReceiveUpdate", $"Document {document.DocumentCode} created!");
            Console.WriteLine("Notification sent successfully");
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine($"Error sending notification: {ex.Message}");
    }
    return Ok(document);
}

查看后端控制台输出,确认消息是否成功发送

5. 测试Hub的消息通道

在Hub中添加测试方法,直接验证消息推送:

public class NotificationHub : Hub
{
    public async Task SendTestMessage(string message)
    {
        await Clients.All.SendAsync("ReceiveUpdate", message);
    }
}

Angular端调用该方法测试:

// 在SignalRService中添加方法
sendTestMessage(message: string) {
  this.hubConnection.invoke('SendTestMessage', message)
    .catch(err => console.error(err));
}

在Angular组件中调用该方法,确认消息通道是否正常

6. 检查SQL Server Broker配置(若依赖数据库自动触发)

若后续要基于数据库变化自动推送通知,需确保:

  • 执行SELECT is_broker_enabled FROM sys.databases WHERE name = 'DMS_DB_1_0_0',确认Broker已启用(返回1)
  • DbContext启用查询通知:
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
            sqlOptions => sqlOptions.EnableRetryOnFailure()
                .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
                .EnableNotifications()));
    

内容的提问来源于stack exchange,提问作者Mohamed Abdelrahman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 15:57:33