ASP.NET Core 8.0 SignalR: успешное подключение, но уведомления на клиенте не получены

Я работаю над приложением веб-API ASP.NET Core 8.0 и впервые пытаюсь реализовать SignalR для уведомлений в реальном времени. Несмотря на успешное подключение к концентратору SignalR, я не могу получать какие-либо уведомления на стороне клиента, будь то в Postman или пользовательском консольном приложении. Мне нужна помощь, чтобы понять, почему уведомления не доставляются.

Что я сделал на данный момент:

  1. Установленный пакет SignalR: я установил пакет Microsoft.AspNetCore.SignalR и настроил свой концентратор.

  2. Создал концентратор и интерфейс. Я создал класс NotificationHub и интерфейс INotificationHub, как показано ниже:

'''

public sealed class NotificationHub : Hub<INotificationHub>
{
  public async Task SendNotification(string userId, ClientNotificationDto notification)
  {
      await Clients.User(userId).ReceiveNotification(notification);
  }

public async Task SendNotificationToGroup(string groupName, string message)
  {
      await Clients.Group(groupName).ReceiveNotification(message);
  }

public async Task AddToGroup(string groupName)
  {
      await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
  }

public async Task RemoveFromGroup(string groupName)
  {
      await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
  }

public async Task SendNotificationToAll(string message)
  {
      await Clients.All.ReceiveNotification(message);
  }
}

public interface INotificationHub
{
    Task ReceiveNotification(string message);
    Task ReceiveNotification(ClientNotificationDto notification);
    Task SendNotification(string userId, ClientNotificationDto notification);
    Task SendNotificationToGroup(string groupName, string message);
    Task AddToGroup(string groupName);
    Task RemoveFromGroup(string groupName);
    Task SendNotificationToAll(string message);
}
  1. Модель ClientNotificationDto. Модель ClientNotificationDto имеет следующую структуру:
    public class ClientNotificationDto
    {
       public long Id { get; set; }
       public string Title { get; set; }
       public string Content { get; set; }
       public bool IsRead { get; set; }
       public DateTimeOffset CreateDate { get; set; }
    }

  1. Настроил SignalR в Program.cs: я добавил службы SignalR и сопоставил концентратор в Program.cs:
services.AddSignalR();
app.MapHub<NotificationHub>("/notificationHub");
  1. Авторизация: я использую атрибут [Authorize], поскольку требуется аутентификация по токену JWT.

  2. Создал тестовый контроллер. Я создал тестовый контроллер для имитации отправки уведомлений:

var notificationDto = new ClientNotificationDto
{
    Id = 1,
    Title = "Test Title",
    Content = "Test Content",
    IsRead = false,
};

await _hubContext.Clients.User(clientNotification.CustomerId).ReceiveNotification(notificationDto);

// Added for testing purposes
await _hubContext.Clients.All.ReceiveNotification("Hello");

Проблема:

  • Когда я запускаю контроллер, он выполняется успешно. Однако когда я подключаюсь к хабу с помощью Postman или специального консольного приложения, я не получаю никаких уведомлений.

  • В Postman и консольном приложении я вижу сообщение Connected to wss://localhost:7159/notificationHub, указывающее, что соединение установлено успешно. Однако никаких сообщений не поступает. В консольном приложении каждые несколько секунд я вижу журналы ping, показывающие, что соединение живо, но сообщений нет.

Что я пробовал:

  • Проверено, что методы NotificationHub вызываются и сообщения должны отправляться.
  • Проверил состояние соединения и логи пингов, которые показывают, что соединение активно.
  • Протестировано как с Postman, так и с пользовательским консольным приложением, но ни одно из них не получает ожидаемых сообщений.

Клиентский код:

class Program
{
    static async Task Main(string[] args)
    {
        var token = "JWT_TOKEN";

        var connection = new HubConnectionBuilder()
            .WithUrl("https://localhost:7159/notification", options =>
            {
                options.AccessTokenProvider = () => Task.FromResult(token);
            })
            .ConfigureLogging(logging =>
            {
                logging.SetMinimumLevel(LogLevel.Debug);
                logging.AddConsole();
            })
            .Build();

        connection.On<ClientNotificationDto>("ReceiveNotification", notification =>
        {
            Console.WriteLine("Received notification event triggered");
            try
            {
                if (notification != null)
                {
                    Console.WriteLine($"Received notification: {notification.Title} - {notification.Content}");
                }
                else
                {
                    Console.WriteLine("Received null notification.");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error processing received notification: {ex.Message}");
            }
        });

        connection.Closed += async (error) =>
        {
            Console.WriteLine($"Connection closed: {error?.Message}");
            await Task.Delay(5000);
            try
            {
                await connection.StartAsync();
                Console.WriteLine("Connection restarted.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error while restarting connection: {ex.Message}");
            }
        };

        try
        {
            await connection.StartAsync();
            Console.WriteLine("Connection started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Connection error: {ex.Message}");
        }

        Console.ReadLine();
    }
}  

Если я добавлю следующую строку в свой основной код:

await _hubContext.Clients.All.ReceiveNotification("hello");

на стороне клиента, где я прослушиваю сообщения, я получаю следующую ошибку:

fail: Microsoft.AspNetCore.SignalR.Client.HubConnection[57]
      Failed to bind arguments received in invocation '(null)' of 'ReceiveNotification'.
      System.IO.InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.
       ---> System.Text.Json.JsonException: The JSON value could not be converted to SignalrTest.ClientNotificationDto. Path: $ | LineNumber: 0 | BytePositionInLine: 129.
         at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
         at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
         at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
         at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
         at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObject(Utf8JsonReader& reader, ReadStack& state)
         at System.Text.Json.JsonSerializer.ReadAsObject(Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo)
         at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindType(Utf8JsonReader& reader, Type type)
         at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
         --- End of inner exception stack trace ---
         at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.BindTypes(Utf8JsonReader& reader, IReadOnlyList`1 paramTypes)
         at Microsoft.AspNetCore.SignalR.Protocol.JsonHubProtocol.ParseMessage(ReadOnlySequence`1 input, IInvocationBinder binder)

Однако, если я удалю эту строку и включу только:

var notificationDto = new ClientNotificationDto
{
    Id = 1,
    Title = "Test Title",
    Content = "Test Content",
    IsRead = false,
};

await _hubContext.Clients.User(clientNotification.CustomerId).ReceiveNotification(notificationDto);

тогда на стороне клиента ничего не получено.

Я не понимаю, почему клиенты не получают уведомления, несмотря на успешное соединение. Что-то мне не хватает в настройке или способе отправки уведомлений? Как устранить эту проблему и заставить уведомления появляться в клиенте?

Любая помощь или предложения будут очень признательны!


1
50
1

Ответ:

Решено

РЕШЕНО

Проблема Я обнаружил проблему при работе с SignalR в своем NotificationHub. Проблема заключалась в том, что клиенты подключались к хабу, но не получали никаких уведомлений, хотя я правильно отправлял их с сервера.

Процесс отладки Чтобы отладить это, я добавил в свой NotificationHub следующий метод:

public override Task OnConnectedAsync()
{
    var userId = Context.UserIdentifier;
    Console.WriteLine($"User connected: {userId}");
    return base.OnConnectedAsync();
}

Когда я проверил значение userId во время отладки, я понял, что ему был присвоен адрес электронной почты пользователя, а не фактический идентификатор клиента (который я передавал при отправке уведомлений). Это несоответствие стало причиной того, что уведомления не доходили до предполагаемых клиентов.

Решение Чтобы решить эту проблему, мне нужно было настроить способ определения UserIdentifier в SignalR. По умолчанию SignalR использует ClaimTypes.NameIdentifier или другое доступное утверждение, которое в моем случае было установлено на адрес электронной почты. Я хотел, чтобы вместо этого использовался идентификатор клиента.

Я создал собственный IUserIdProvider, чтобы переопределить это поведение:

public class CustomUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst("customerId")?.Value;
    }
}

Затем я зарегистрировал этого специального поставщика в Program.cs:

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

Результат После реализации этого пользовательского UserIdProvider все стало работать правильно. Context.UserIdentifier теперь правильно отражает идентификатор клиента, а уведомления отправляются и принимаются, как и ожидалось.


Интересные вопросы для изучения

Почему сведения о проблеме не возвращаются в ответе HTTP из моей реализации IExceptionHandler?Можно ли обернуть (заменить) зарегистрированный сервис своим, но при этом сохранить старый экземпляр?Свойство навигации EF Core без отношений внешнего ключаEntity Framework помещает случайный GUID в строку. Пустое поле при сохранении базы данных.NET MAUI VS 2022 Как указать целевую версию Android?Как я могу исправить связь многие-ко-многим между двумя объектами, чтобы третья таблица автоматически заполнялась в структуре сущностей и веб-API (С#)Как динамически устанавливать дополнительные свойства в моделях с несколькими запросами в ASP.NET Core Web ApiОшибка анализа Json при использовании метода /login из WebApiCore Identity, предоставленного [email protected]Получение ошибки при подключении веб-API к PostgreSQLASP.NET Entity Framework Core — ограничение внешнего ключа связи «один ко многим», блокирующее запрос PUT