Mr Perfect
Mr Perfect

Reputation: 685

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

Upvotes: 0

Views: 1423

Answers (2)

Md Farid Uddin Kiron
Md Farid Uddin Kiron

Reputation: 22543

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.

Upvotes: 2

thanzeel
thanzeel

Reputation: 602

i have my setup like this

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
   app.UseSignalR(routes =>
        {
            routes.MapHub<ChatHub>("/chat");
        });
}




public void ConfigureServices(IServiceCollection services)
        {
 services.AddAuthentication()
              .AddJwtBearer(cfg =>
              {
                  cfg.TokenValidationParameters = new TokenValidationParameters()
                  {
                      ValidIssuer = configuration["Tokens:Issuer"],
                      ValidAudience = configuration["Tokens:Audience"],
                      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Tokens:Key"]))
                  };

                  cfg.Events = new JwtBearerEvents
                  {
                      OnMessageReceived = context =>
                      {
                          var accessToken = context.Request.Query["access_token"];

                          // If the request is for our hub...
                          var path = context.HttpContext.Request.Path;
                          if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chat")))
                          {
                              // Read the token out of the query string
                              context.Token = accessToken;
                          }
                          return Task.CompletedTask;
                      }
                  };
              });
}

And the class that inherits the Hub like below

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class ChatHub : Hub
    {
        

        public async Task SendMessage(MessageModel msg)
        {   
            if (!string.IsNullOrEmpty(msg.ClientUniqueId))
            {
                await Clients.Client(msg.ClientUniqueId).SendAsync("ReceiveMessage", chat);

            }
        }
      
    }

Upvotes: 0

Related Questions