.NET 7中Blazor WASM无法接收ASP.NET Core Web API的SignalR消息
Blazor WASM无法接收SignalR通知(CQRS投影场景)
问题背景
在.NET 7环境下搭建Blazor WebAssembly客户端+ASP.NET Core Web API服务端,采用CQRS模式开发时,查询投影无法即时就绪,因此通过SignalR向客户端推送投影的创建/更新状态,但客户端始终无法收到SignalR事件,自查服务端消息发送逻辑未发现问题,请求排查。
CQRS完整流程
- Blazor WASM客户端向Web API发送POST/PUT请求
- Web API控制器获取SignalR连接ID(代码中称为“client ID”)并传入MediatR请求
- MediatR管道行为通过
IHttpContextAccessor将该ID存入HTTP上下文 SaveChangesInterceptor从上下文读取ID,关联到数据库中的领域事件- 领域事件分发器发布包含该ID的事件
- 领域事件处理器创建/更新查询投影,并尝试发送SignalR通知
排查方向建议
1. SignalR连接与ID一致性校验
- 对比客户端与服务端的连接ID:客户端在
HubConnection建立成功后,输出_hubConnection.ConnectionId;服务端在API控制器接收请求时,打印传入的client ID,确认二者完全匹配(注意大小写和格式) - 检查SignalR配置:
- 服务端是否已添加SignalR服务(
builder.Services.AddSignalR())并映射Hub端点(app.MapHub<NotificationHub>("/notification-hub")) - 客户端是否使用正确的Hub地址建立连接,确保地址与服务端映射路径一致
- 服务端是否已添加SignalR服务(
2. 领域事件中client ID的传递校验
- 在
SaveChangesInterceptor中添加日志,输出读取到的client ID值,确认从HTTP上下文成功获取 - 直接查询数据库中的领域事件记录,验证client ID字段已被正确赋值
3. SignalR消息发送的上下文与方法校验
- 检查领域事件处理器的执行上下文:如果处理器是在后台线程执行(如MediatR后台任务),确认
IHubContext能被正确注入,不存在DI范围失效问题 - 确认消息发送方法正确性:确保调用
Clients.Client(clientId).SendAsync("EventName", payload),且事件名称与客户端订阅的名称完全一致(大小写敏感)
4. 客户端订阅逻辑校验
- 确保订阅操作在
HubConnection.StartAsync()完成后执行,避免连接未就绪时订阅失效 - 检查订阅的事件名称与服务端发送的名称完全匹配,无大小写或拼写错误
- 为订阅添加错误处理,捕获并输出可能的订阅异常:
_hubConnection.On<ProjectionUpdatedDto>("ProjectionUpdated", dto => { /* 处理逻辑 */ }, error => Console.WriteLine($"订阅错误: {error.Message}") );
关键代码片段
服务端API控制器
[HttpPost] public async Task<IActionResult> Create([FromBody] CreateCommand command) { var clientId = HttpContext.Request.Headers["X-SignalR-ClientId"].FirstOrDefault(); command.ClientId = clientId; await _mediator.Send(command); return Ok(); }
MediatR管道行为
public class ClientIdBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IClientIdRequest { private readonly IHttpContextAccessor _httpContextAccessor; public ClientIdBehavior(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(request.ClientId)) { _httpContextAccessor.HttpContext.Items["ClientId"] = request.ClientId; } return await next(); } }
SaveChangesInterceptor
public class DomainEventInterceptor : SaveChangesInterceptor { private readonly IHttpContextAccessor _httpContextAccessor; public DomainEventInterceptor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { var clientId = _httpContextAccessor.HttpContext?.Items["ClientId"] as string; if (!string.IsNullOrEmpty(clientId)) { var domainEntities = eventData.Context.ChangeTracker .Entries<Entity>() .Where(e => e.Entity.DomainEvents.Any()) .Select(e => e.Entity); foreach (var entity in domainEntities) { foreach (var domainEvent in entity.DomainEvents) { ((IDomainEventWithClientId)domainEvent).ClientId = clientId; } } } return base.SavingChangesAsync(eventData, result, cancellationToken); } }
领域事件处理器
public class ProjectionUpdatedHandler : INotificationHandler<ProjectionUpdatedEvent> { private readonly IHubContext<NotificationHub> _hubContext; public ProjectionUpdatedHandler(IHubContext<NotificationHub> hubContext) { _hubContext = hubContext; } public async Task Handle(ProjectionUpdatedEvent notification, CancellationToken cancellationToken) { // 创建/更新投影逻辑... if (!string.IsNullOrEmpty(notification.ClientId)) { await _hubContext.Clients.Client(notification.ClientId) .SendAsync("ProjectionUpdated", new ProjectionUpdatedDto(), cancellationToken); } } }
客户端SignalR订阅
private HubConnection _hubConnection; protected override async Task OnInitializedAsync() { _hubConnection = new HubConnectionBuilder() .WithUrl(NavigationManager.ToAbsoluteUri("/notification-hub")) .Build(); _hubConnection.On<ProjectionUpdatedDto>("ProjectionUpdated", dto => { // 处理通知逻辑 StateHasChanged(); }); try { await _hubConnection.StartAsync(); } catch (Exception ex) { Console.WriteLine($"连接失败: {ex.Message}"); } }
内容的提问来源于stack exchange,提问作者TheMagnificent11
相关产品推荐
相关产品推荐

