I am working on an ASP.NET Core 8.0 Web API application and I'm trying to implement SignalR for real-time notifications for the first time. Despite successfully connecting to the SignalR hub, I am unable to receive any notifications on the client side, whether it's in Postman or a custom console application. I need help to understand why the notifications are not being delivered.
What I've Done So Far:
Installed SignalR Package: I have installed the Microsoft.AspNetCore.SignalR package and set up my hub.
Created the Hub and Interface: I created a NotificationHub class and an INotificationHub interface as shown below:
'''
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");
Authorization: I am using the [Authorize] attribute because JWT token authentication is required.
Created a Test Controller: I created a test controller to simulate sending notifications:
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");
The Problem:
When I trigger the controller, it executes successfully. However, when I connect to the hub using Postman or a custom console application, I don't receive any notifications.
In Postman and the console application, I see the message Connected to wss://localhost:7159/notificationHub, indicating that the connection is successful. However, no messages are received. In the console application, I see ping logs every few seconds, showing that the connection is alive, but there are no messages.
What I've Tried:
Client Side Code:
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();
}
}
If I add the following line to my main code:
await _hubContext.Clients.All.ReceiveNotification("hello");
on the client-side, where I'm listening for messages, I get the following error:
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)
However, if I remove that line and only include:
var notificationDto = new ClientNotificationDto
{
Id = 1,
Title = "Test Title",
Content = "Test Content",
IsRead = false,
};
await _hubContext.Clients.User(clientNotification.CustomerId).ReceiveNotification(notificationDto);
then nothing is received on the client side.
I'm at a loss as to why the notifications are not being received by the clients, despite the connection being successful. Is there something I'm missing in the setup or the way the notifications are being sent? How can I troubleshoot this issue and get the notifications to appear in the client?
Any help or suggestions would be greatly appreciated!
Issue I discovered a problem while working with SignalR in my NotificationHub. The issue was that clients were connecting to the hub, but they weren't receiving any notifications, even though I was sending them correctly from the server.
Debugging Process To debug this, I added the following method in my NotificationHub:
public override Task OnConnectedAsync()
{
var userId = Context.UserIdentifier;
Console.WriteLine($"User connected: {userId}");
return base.OnConnectedAsync();
}
When I checked the value of userId during debugging, I realized that it was being set to the user's email address instead of the actual customer ID (which I was passing when sending notifications). This mismatch was the reason the notifications weren't reaching the intended clients.
Solution To resolve this, I needed to customize how SignalR determines the UserIdentifier. By default, SignalR uses the ClaimTypes.NameIdentifier or another available claim, which in my case was set to the email address. I wanted it to use the customerId instead.
I created a custom IUserIdProvider to override this behavior:
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst("customerId")?.Value;
}
}
Then, I registered this custom provider in Program.cs:
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
Result After implementing this custom UserIdProvider, everything started working correctly. The Context.UserIdentifier now correctly reflects the customerId, and notifications are being sent and received as expected.