Search code examples
c#asp.net-coresignalrazure-signalr

How to send messages to specific user using Azure SignalR in .NET Core


.NET Core API application which I want to use push real time messages to SPA. I have working example with azure function but now I want to convert it to Web API.

The working Azure function looks like this:

[FunctionName("Push")]
public static Task PushInfoSuccess([HttpTrigger(AuthorizationLevel.Anonymous, "post")] ILogger log, Models models
           [SignalR(HubName = "Hub1")] IAsyncCollector<SignalRMessage> signalRMessages)
{
     return signalRMessages.AddAsync(
               new SignalRMessage
               {
                   UserId = models.UserId,
                   Target = "Hub1",
                   Arguments = new[] { models}
               });
 }

I want to rewrite using .NET Core API.

I created hub class like below

public class ChatHub : Hub
{
    public Task BroadcastMessage(string name, string message) =>
        Clients.All.SendAsync("broadcastMessage", name, message);

    public void Send(UserModel userModel)
    {
        Clients.User(userModel.UserId).SendAsync(userModel.Message);
    }

    public Task Echo(string name, string message) =>
        Clients.Client(Context.ConnectionId)
               .SendAsync("echo", name, $"{message} (echo from server)");
}

I have this model class:

public class UserModel
{
    public  string UserId { get; set; }
    public string Message { get; set; }
}

Now I have some other application which will call my application through API so I will add controller

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public ActionResult Get(UserModel userModel)
    {
        return Ok();
    }
}

My other application will invoke this API to push notification to SPA. When pushing notification I want to push it to specific userid which I will get UserID and Message through API. Now I want to push messages to UserModel.UserID. When sending message to specific user, Do I need to consider connection Id as well? If I have multiple hubs then do I get different connectionid for each hub? In my SPA application I have more than one hub. So what would be the relationship between the connection id and userid? Can someone help me here to understand and help me? Thanks


Solution

  • Connectionids plus userid will become uniquness right?

    Yes right you are, This should be like below:

    HubController:

    public class HubController : Controller
        {
           
            private readonly IHubContext<NotificationUserHub> _notificationUserHubContext;
            private readonly IUserConnectionManager _userConnectionManager;
    
            public HubController(IHubContext<NotificationHub> notificationHubContext, IHubContext<NotificationUserHub> notificationUserHubContext, IUserConnectionManager userConnectionManager)
            {
              
                _notificationUserHubContext = notificationUserHubContext;
                _userConnectionManager = userConnectionManager;
            }
            
    
            [HttpPost]
            public async Task<ActionResult> SendToSpecificUser(HubModel model)
            {
                var connections = _userConnectionManager.GetUserConnections(model.userId);
                if (connections != null && connections.Count > 0)
                {
                    foreach (var connectionId in connections)
                    {
                        await _notificationUserHubContext.Clients.Client(connectionId).SendAsync("sendToUser", model.Title, model.Message);
                    }
                }
                return View();
            }
        }
    }
    

    Notification User Hub:

    public class NotificationUserHub : Hub
        {
            private readonly IUserConnectionManager _userConnectionManager;
            public NotificationUserHub(IUserConnectionManager userConnectionManager)
            {
                _userConnectionManager = userConnectionManager;
            }
            public string GetConnectionId()
            {
                var httpContext = this.Context.GetHttpContext();
                var userId = httpContext.Request.Query["userId"];
                _userConnectionManager.KeepUserConnection(userId, Context.ConnectionId);
    
                return Context.ConnectionId;
            }
    
            //Called when a connection with the hub is terminated.
            public async override Task OnDisconnectedAsync(Exception exception)
            {
                //get the connectionId
                var connectionId = Context.ConnectionId;
                _userConnectionManager.RemoveUserConnection(connectionId);
                var value = await Task.FromResult(0);//adding dump code to follow the template of Hub > OnDisconnectedAsync
            }
        }
    

    User Connection Manager:

    public class UserConnectionManager : IUserConnectionManager
        {
            private static Dictionary<string, List<string>> userConnectionMap = new Dictionary<string, List<string>>();
            private static string userConnectionMapLocker = string.Empty;
    
            public void KeepUserConnection(string userId, string connectionId)
            {
                lock (userConnectionMapLocker)
                {
                    if (!userConnectionMap.ContainsKey(userId))
                    {
                        userConnectionMap[userId] = new List<string>();
                    }
                    userConnectionMap[userId].Add(connectionId);
                }
            }
    
            public void RemoveUserConnection(string connectionId)
            {
                //Remove the connectionId of user 
                lock (userConnectionMapLocker)
                {
                    foreach (var userId in userConnectionMap.Keys)
                    {
                        if (userConnectionMap.ContainsKey(userId))
                        {
                            if (userConnectionMap[userId].Contains(connectionId))
                            {
                                userConnectionMap[userId].Remove(connectionId);
                                break;
                            }
                        }
                    }
                }
            }
            public List<string> GetUserConnections(string userId)
            {
                var conn = new List<string>();
                lock (userConnectionMapLocker)
                {
                    conn = userConnectionMap[userId];
                }
                return conn;
            }
        }
    

    Model:

    public class HubModel 
        {
            public string Title { get; set; }
            public string Message { get; set; }
            public string userId { get; set; }
        }
    

    Hope it will help you.