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

ASP.NET Core SignalR+Blazor中WebSocket连接失败问题排查求助

SignalR WebSocket连接失败排查与解决方案

问题描述

使用ASP.NET Core API结合SignalR与Blazor客户端实现通知系统,已配置NotificationHub和自定义用户ID提供者管理用户连接,但启动应用时始终出现WebSocket连接失败错误:

WebSocket connection to 'wss://localhost:63225/notificationHub?id=LDfet**&access_token=ey**' failed:

已验证access_token生成正确且用户已完成认证,同时在NotificationHub中实现OnConnectedAsync方法记录用户标识符,Blazor客户端通过IAccessTokenProvider正确获取令牌。

相关代码片段

NotificationHub

[Authorize]
public class NotificationHub : Hub
{
    public override Task OnConnectedAsync()
    {
        Debug.WriteLine(Context.UserIdentifier);
        return base.OnConnectedAsync();
    }
}

CustomUserIdProvider

public class CustomUserIdProvider : IUserIdProvider
{
    public string? GetUserId(HubConnectionContext connection)
    {
        return connection.User?.Identity?.Name;
    }
}

添加请求并发送通知的业务代码

public async Task<int> AddEvolutionDemand(EvolutionDto evolutionToAddDto)
{
    var EvolutionDemand = new Evolution();
    try
    {
        EvolutionDemand = new()
        {
            Service_ID = evolutionToAddDto.Service_ID,
            User_ID = evolutionToAddDto.User_ID,
            User_Name = evolutionToAddDto.User_Name,
            Doc_ID = evolutionToAddDto.Doc_ID,
            Demande_Creation_Date = evolutionToAddDto.Demande_Creation_Date,
            Demand_Type = evolutionToAddDto.Demand_Type,
            Demand_Title = evolutionToAddDto.Demand_Title,
            Demande_Comment = evolutionToAddDto.Demande_Comment,
            STATUS = evolutionToAddDto.STATUS,
            Demand_Assigned_ID = evolutionToAddDto.Demand_Assigned_ID,
            Demand_update_date = evolutionToAddDto.Demand_update_date,
            Demand_Update_Comment = evolutionToAddDto.Demand_Update_Comment,
        };
        await _kmDbContext.Evolution.AddAsync(EvolutionDemand);
        await _kmDbContext.SaveChangesAsync();
        var UserToNotify = await UserRepository.GetUser(evolutionToAddDto.Demand_Assigned_ID);
        await HubConntextion.Clients.User(UserToNotify.User_Login).SendAsync("notification", $"{DateTime.Now}");
    }
    catch (Exception ex)
    {
        _logger.Error(ex, ErrorMessage, ex.Message);
    }
    return EvolutionDemand.Demand_ID;
}

Blazor客户端代码

@code {
    private HubConnection? hubConnection;
    public int pendingDemandCount { get; set; } = 0;
    List<string> notifications = [];
    [Inject] IAccessTokenProvider AccessTokenProvider { get; set; } = default!;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        var authState = await AuthenticationStateTask;

        if (authState.User.Identity != null && authState.User.Identity.IsAuthenticated)
        {
            await connectToNotificationHub();
            await RefreshPendingDemands();
        }
    }

    public async Task connectToNotificationHub()
    {
        try
        {
            if (hubConnection is not null)
                return;
            var notificationUrl = "notificationHub";
            hubConnection = new HubConnectionBuilder()
                                .WithUrl($"https://localhost:63225/{notificationUrl}", options =>
                                {
                                    options.AccessTokenProvider = () => GetAccessTokenAsync();
                                })
                                .Build();

            hubConnection.On<string>("notification", async (message) =>
            {
                notifications.Add(message);
                await RefreshPendingDemands();
                await InvokeAsync(StateHasChanged);
            });

            await hubConnection.StartAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error connecting to SignalR hub: {ex.Message}");
            Console.WriteLine($"Stack trace: {ex.StackTrace}");
        }
    }

    private async Task<string?> GetAccessTokenAsync()
    {
        var tokenResult = await AccessTokenProvider.RequestAccessToken();
        if (tokenResult != null && tokenResult.TryGetToken(out AccessToken accessToken))
        {
            return accessToken.Value;
        }
        return null;
    }

    private async Task RefreshPendingDemands()
    {
        var pendingDemands = await DocumentService.GetPendingDemandsForUser();
        pendingDemandCount = pendingDemands?.Count ?? 0;
    }

    public async ValueTask DisposeAsync()
    {
        if (hubConnection is not null)
        {
            await hubConnection.DisposeAsync();
            hubConnection = null;
        }
    }
}

排查与解决步骤

1. 检查服务端SignalR路由配置

确认在Program.cs中正确映射SignalR hub,且顺序在认证中间件之后:

// 先添加认证中间件
app.UseAuthentication();
app.UseAuthorization();

// 再映射SignalR hub
app.MapHub<NotificationHub>("/notificationHub");

若路由映射在认证中间件之前,会导致[Authorize]特性无法正确验证令牌,直接拒绝连接。

2. 验证令牌权限与声明

  • 用JWT解析工具查看access_token,确认包含Identity.Name对应的声明(如name或unique_name),因为自定义CustomUserIdProvider依赖该值。
  • 检查令牌过期时间,避免因令牌过期导致连接失败。
  • 若使用自定义授权策略,确认令牌包含对应角色或声明。

3. 检查CORS配置

Blazor客户端与API跨域时,需确保CORS策略允许SignalR的WebSocket请求:

builder.Services.AddCors(options =>
{
    options.AddPolicy("SignalRCors", policy =>
    {
        policy.WithOrigins("https://your-blazor-client-url")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials() // 必须启用,SignalR会携带凭证
              .WithExposedHeaders("WWW-Authenticate");
    });
});

app.UseCors("SignalRCors");

4. 开启服务端详细日志

在appsettings.json中配置日志级别,捕获连接过程中的具体错误:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.AspNetCore.SignalR": "Debug",
      "Microsoft.AspNetCore.Http.Connections": "Debug"
    }
  }
}

启动服务后查看控制台日志,可获取连接失败的具体原因(如认证失败、路由不存在等)。

5. 验证客户端连接配置

  • 确保客户端连接地址与服务端一致,避免HTTP/HTTPS混用。
  • 尝试强制使用WebSocket传输,排除其他传输方式干扰:
hubConnection = new HubConnectionBuilder()
                    .WithUrl($"https://localhost:63225/{notificationUrl}", options =>
                    {
                        options.AccessTokenProvider = () => GetAccessTokenAsync();
                        options.Transports = HttpTransportType.WebSockets;
                    })
                    .Build();

6. 确认CustomUserIdProvider注册

在Program.cs中正确注册自定义用户ID提供者:

builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();

未注册会导致SignalR使用默认提供者,无法正确获取用户标识符。

7. 检查HubContext注入

业务服务中确保IHubContext<NotificationHub>正确注入,避免后续推送功能异常:

private readonly IHubContext<NotificationHub> _hubContext;

public YourService(IHubContext<NotificationHub> hubContext)
{
    _hubContext = hubContext;
}

内容的提问来源于stack exchange,提问作者khalil hajri

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 09:37:07