Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send Message to Specific user using Azure SignalR

Using Azure SignalR (not .Net Core SignalR), Is Clients.Users.SendAsync supported to send message to specific user?

[Authorize]
public class ChatHub : Hub
{
    public async Task OnNewMessage(string userId, string message)
    {
        await Clients.Users(userId) 
             //Does Azure SignalR supports sending message to specific user,
             // the way .Net Core SignalR does it
            .SendAsync("OnNewMessage", userId, message);
    }
}

If yes, How Asp.Net Identity Claims Principal gets passed to Azure SignalR?

Edit: 18th June 2021

Responses below are not addressing the concerns. Sorry but I had already followed documentation for customizing UserIdBasedProvider etc. I want to stress out that -

This question is pertaining to how SignalR handles .Users(<*some-user-id*>).Send();

Ideally we do .Client(<connection id>).Send() to send message to specific user. But for this architecture we must store userId and connectionId ourselves in DB etc.

.User(userId).Send() works perfectly with .Net Core SignalR. So I believe SignalR SDK must have been keeping map of each connection Id and user Id in memory.

Does Azure SignalR internally keeps track of UserId, Connection Id mapping and sends the message?

like image 638
Abhijeet Avatar asked Jun 09 '21 11:06

Abhijeet


People also ask

How do I send a message to a specific user in SignalR?

In SignalR 2.0, this is done by using the inbuilt IPrincipal.Identity.Name , which is the logged in user identifier as set during the ASP.NET authentication. However, you may need to map the connection with the user using a different identifier instead of using the Identity.Name.

Is SignalR scalable?

A SignalR app can scale out based on the number of messages sent, while the Azure SignalR Service scales to handle any number of connections.


Video Answer


1 Answers

Yes, if you'd like to implement serverless arhcitecture you should use Azure Functions for that. The typical serverless architecture is going to look like this

azure signalr architecture

The client (i.e. browser) sends a negotiation request to an azure function, the response continas all information required to establish connection with Azure SignalR. You should attach JWT token to a negotiation request. How you obtain a JWT token is up to you. Attaching it is pretty simple just provide a token factory when you create a signalR connection

const connection = new signalR.HubConnectionBuilder()
    .withUrl('link to your azure functions api', {
        accessTokenFactory: () => {
            return 'your token'
        }
    })
    .build();

For this to work you also need to set up an upstream connection so that your Azure SignalR service can forward all events to your Azure Function. Azure funciton handles upstreamed events and sends requests to an Azure SignalR to broadcast data to given user or multiple users.

upstream set up

The code for the SendMessage method in a hub is very similar to regular hubs except for Azure funciton bindings. You'll also need to extend ServerlessHub class instead of Hub and to install Microsoft.Azure.WebJobs.Extensions.SignalRService package

public class ChatHub : ServerlessHub
{

    [FunctionName(nameof(SendToUser))]
    public async Task SendToUser([SignalRTrigger] InvocationContext invocationContext, string userName, string message)
    {
        await Clients.User(userName).SendAsync("new_message", new NewMessage(invocationContext, message));
    }

    [FunctionName("negotiate")]
    public SignalRConnectionInfo Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req)
    {
        var claims = GetClaims(req.Headers["Authorization"]);
        var userName = claims.First(x => x.Type == ClaimTypes.NameIdentifier);
        return Negotiate(userName.Value, claims);
    }


    private class NewMessage
    {
        public string ConnectionId { get; }
        public string Sender { get; }
        public string Text { get; }

        public NewMessage(InvocationContext invocationContext, string message)
        {
            Sender = string.IsNullOrEmpty(invocationContext.UserId) ? string.Empty : invocationContext.UserId;
            ConnectionId = invocationContext.ConnectionId;
            Text = message;
        }
    }
}

You can also find a code sample with step by step instructions here

Edit:

If you don't want to go completely serverless and want to use regular web app it's also perfectly fine. The architecture diagram would look like this

Web app architecture

And you should use regular hub class in this case. The code you posted should work just fine.

The connection management is done completely by Azure Signalr service, you don't need to do any additional synchronization and you don't need to store connection data in a database. Just set up the tier size and the number of units.

Here's the short description of how it works:

  1. Client (e.g. Js client in browser) sends request to a web app to a negotiate endpoint and receives link to azure signalr service and an access token. This is a JWT token. A JWT token consists of several parts. The second part of that token is the payload, which contains the claims.
  2. Using the data fetched from negotiate endpoint client connects to azure signalr service.

So the Azure SignalR service uses this access token, it receives from negotiate endpoint, to authenticate a user. You don't have to write any additional code for that it's all done under the hood by the library.

In case you use a web app, the negotiate request is handled by the SignalR Sdk, you don't need to write any addional code for that.

It's all described in this article

And you can find detailed guide about implementing authentication here

like image 131
Alexander Mokin Avatar answered Oct 22 '22 20:10

Alexander Mokin