Я работаю над приложением веб-API ASP.NET Core 8.0 и впервые пытаюсь реализовать SignalR для уведомлений в реальном времени. Несмотря на успешное подключение к концентратору SignalR, я не могу получать какие-либо уведомления на стороне клиента, будь то в Postman или пользовательском консольном приложении. Мне нужна помощь, чтобы понять, почему уведомления не доставляются.
Что я сделал на данный момент:
Установленный пакет SignalR: я установил пакет Microsoft.AspNetCore.SignalR и настроил свой концентратор.
Создал концентратор и интерфейс. Я создал класс 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);
}
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; }
}
services.AddSignalR();
app.MapHub<NotificationHub>("/notificationHub");
Авторизация: я использую атрибут [Authorize], поскольку требуется аутентификация по токену JWT.
Создал тестовый контроллер. Я создал тестовый контроллер для имитации отправки уведомлений:
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, показывающие, что соединение живо, но сообщений нет.
Что я пробовал:
Клиентский код:
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);
тогда на стороне клиента ничего не получено.
Я не понимаю, почему клиенты не получают уведомления, несмотря на успешное соединение. Что-то мне не хватает в настройке или способе отправки уведомлений? Как устранить эту проблему и заставить уведомления появляться в клиенте?
Любая помощь или предложения будут очень признательны!
Проблема Я обнаружил проблему при работе с 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 теперь правильно отражает идентификатор клиента, а уведомления отправляются и принимаются, как и ожидалось.