dcp
dcp

Reputation: 55467

signalR - send message to specific user

I'm working through the SignalR Microsoft tutorial located here.

Everything is working ok, however, I wanted to try to change the code in ChatHub#SendMessage to send a message to a specific user.

So I modified the code like this:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        //await Clients.All.SendAsync("ReceiveMessage", user, message);

        await Clients.User(user).SendAsync("ReceiveMessage", message);
    }
}

However, the user never gets the message. The message is only sent if you use Clients.All.SendAsync (that's the line that's commented out above).

I also did more research and found from this answer that maybe I need to implement a custom user provider. So I implemented one:

public class CustomUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        return "user1";
    }
}

Then in Startup.cs, I registered this provider:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
    services.AddSignalR();
}

Then I tried using the Context.UserIdentifier when sending the message, like this:

public async Task SendMessage(string user, string message)
{
    //await Clients.All.SendAsync("ReceiveMessage", user, message);

    await Clients.User(Context.UserIdentifier).SendAsync("ReceiveMessage", message);
}

But it still does not seem to send the message.

What am I doing wrong?

Upvotes: 1

Views: 4743

Answers (2)

Andy
Andy

Reputation: 13607

The way I do it is when a user logs in I add them to a SignalR Group, the Group being the user's ID or Name. that way it's a unique group for just that one user. Then I send the message to that Group. Only that user will get that message because they are the only one in that group.

Client Side Code:

// Optional: authenticate with a server, get a token... or send username/password in following request

_hub = new HubConnectionBuilder()
    .WithUrl(_envSettings.CreatePlayerServiceHubPath(), x =>
    {
        x.Headers.Add("x-lcc-version", AppEnvironment.Application.Version);
        x.Headers.Add("x-lcc-username", username);
        x.Headers.Add("x-lcc-authorization", token);
    })
    .AddNewtonsoftJsonProtocol()
    .Build();

_hub.Closed += OnSignalRDisconnectedFromServerAsync;
SetupRPCs();
await _hub.StartAsync().ConfigureAwait(false);

Server Side Code:

public override async Task OnConnectedAsync()
{
    var userName = Context.GetHttpContext().Request.Headers["x-lcc-username"];
    var token = Context.GetHttpContext().Request.Headers["x-lcc-authorization"];
    
    // Optional: validate authorization
    
    await Groups.AddToGroupAsync(Context.ConnectionId, userName);
    
    // Then when you want to send a message to the user:
    await Clients.Group(userName).SendAsync("hello", "world");
}

What's nice is that when the user disconnects, the Group automatically disappears. You don't have to maintain those groups you are creating.

I know this may not be 100% what you are looking for, but it's an idea of how to handle similar situations with other scenarios.

Upvotes: 3

dcp
dcp

Reputation: 55467

I found the issue. I'd left off the user parameter in the Clients.User(user).SendAsync call.

So it should be like this:

public async Task SendMessage(string user, string message)
{
    //await Clients.All.SendAsync("ReceiveMessage", user, message);

    await Clients.User(user).SendAsync("ReceiveMessage", user, message); // <-- add user parameter
}

Upvotes: 3

Related Questions