10

I have a signalR Server(Console Application) and a client application(Asp.net MVC5)

How I can send message to specific user in OAuth Membership.

Actually I can't resolve sender user from hub request context with.

Context.User.Identity.Name

My Hub

public class UserHub : Hub
{

    #region Hub Methods
    public void LoggedIn(string userName, string uniqueId, string ip)
    {
        Clients.All.userLoggedIn(userName, uniqueId, ip);
    }
    public void LoggedOut(string userName, string uniqueId, string ip)
    {
        var t = ClaimsPrincipal.Current.Identity.Name;
        Clients.All.userLoggedOut(userName, uniqueId, ip);
    }
    public void SendMessage(string sendFromId, string userId, string sendFromName, string userName, string message)
    {
        Clients.User(userName).sendMessage(sendFromId, userId, sendFromName, userName, message);
    }
    #endregion
}

Start hub class(Program.cs)

class Program
{
    static void Main(string[] args)
    {
        string url = string.Format("http://localhost:{0}", ConfigurationManager.AppSettings["SignalRServerPort"]);
        using (WebApp.Start(url))
        {
            Console.WriteLine("Server running on {0}", url);
            Console.ReadLine();
        }
    }
}
Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
Mahdi.momtaheni
  • 113
  • 1
  • 1
  • 8
  • even if you already accepted the answer here below, I suggest you give a look at this: http://stackoverflow.com/a/21355406/720780 – Wasp Jun 30 '15 at 11:31

4 Answers4

30

Keep connectionId with userName by creating a class as we know that Signalr only have the information of connectionId of each connected peers.

Create a class UserConnection

Class UserConnection{
  public string UserName {set;get;}
  public string ConnectionID {set;get;}
}

Declare a list

List<UserConnection> uList=new List<UserConnection>();

pass user name as querystring during connecting from client side

$.connection.hub.qs = { 'username' : 'anik' };

Push user with connection to this list on connected mthod

public override Task OnConnected()
{
    var us=new UserConnection();
    us.UserName = Context.QueryString['username'];
    us.ConnectionID =Context.ConnectionId;
    uList.Add(us);
    return base.OnConnected();
}

From sending message search user name from list then retrive the user connectionid then send

var user = uList.Where(o=>o.UserName ==userName);
if(user.Any()){
   Clients.Client(user.First().ConnectionID ).sendMessage(sendFromId, userId, sendFromName, userName, message);
}

DEMO

Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
  • 2
    This can be done in a quite simpler way by implementing a custom `IUserIdProvider` implementation, where you can handle your query string for example, and then using the `Clients.User` method. No need to mantain any separate list of connection ids. – Wasp Jun 30 '15 at 08:42
  • For a .Net client, `var connection = new HubConnection(url, queryString)` can be used. Eg: `var connection = new HubConnection("http://localhost:8080/","name=abc")` – ibubi May 11 '17 at 09:57
  • Can this `sendMessage` be dynamic ? -> Any way to pass this `sendMessage` method name as `string`, similar to how we use `Invoke` method. ? – Sreekumar P May 29 '17 at 11:02
  • 1
    I have two questions here. 1.) Suppose if a user opens a new tab, then there will be two different connection ids. So in that case, we will have two connection ids for a single user. and have to send message to both connections. Right? 2.) Maintaining a list is a good practice. Suppose, If we have thousands of users then this list will be quite big. Is there any other optimized way available to achieve the same result? – Ankush Jain Dec 14 '18 at 09:50
  • 1
    @AnkushJain 1) check a users' connection exists or not based on his/her userid before assigning him/her to the list. if the user exists, you can either update the connection id or discard the new connection id which is up to you. 2) if the list is big then, use a small database which query is faster. It's totally based on your system requirements. – Anik Islam Abhi Dec 15 '18 at 02:39
11

All of these answers are unnecessarily complex. I simply override "OnConnected()", grab the unique Context.ConnectionId, and then immediately broadcast it back to the client javascript for the client to store and send with subsequent calls to the hub server.

public class MyHub : Hub
{
    public override Task OnConnected()
    {
        signalConnectionId(this.Context.ConnectionId);
        return base.OnConnected();
    }

    private void signalConnectionId(string signalConnectionId)
    {
        Clients.Client(signalConnectionId).signalConnectionId(signalConnectionId);
    }
}

In the javascript:

$(document).ready(function () {

    // Reference the auto-generated proxy for the SignalR hub. 
    var myHub = $.connection.myHub;

    // The callback function returning the connection id from the hub
    myHub.client.signalConnectionId = function (data) {
        signalConnectionId = data;
    }

    // Start the connection.
    $.connection.hub.start().done(function () {
        // load event definitions here for sending to the hub
    });

});
Bob Quinn
  • 139
  • 1
  • 4
  • 1
    I think there's a problem with your solution. What if the user leaves the page before being notified about an action he/she started? Wouldn't the new connection ID be different and therefore not be sent to this user? For that reason, saving the relationship between connection ID and user ID makes sense. That way even if that scenario is hit you will receive the message because you used the immutable user ID as reference. – eestein May 16 '17 at 16:14
1

In order to be able to get "Context.User.identity.Name", you supposed to integrate your authentication into OWIN pipeline.

More info can be found in this SO answer: https://stackoverflow.com/a/52811043/861018

Illidan
  • 4,047
  • 3
  • 39
  • 49
1

In ChatHub Class Use This for Spacific User

public Task SendMessageToGroup(string groupName, string message)
    {

        return Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId}: {message}");
    }

    public async Task AddToGroup(string groupName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);

        await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}.");
    }

    public async Task RemoveFromGroup(string groupName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);

        await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}.");
    }