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);

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

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

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

нам нужно увидеть код клиента (получение сообщений)

Jerdine Sabio 26.08.2024 16:48

@JerdineSabio Здравствуйте, я обновил код и включил код на стороне клиента вместе с сообщением об ошибке, с которым столкнулся.

Levan Amashukeli 26.08.2024 17:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 теперь правильно отражает идентификатор клиента, а уведомления отправляются и принимаются, как и ожидалось.

Другие вопросы по теме