Mapping SignalR connections to users Mapping SignalR connections to users asp.net asp.net

Mapping SignalR connections to users


Check out the following blog post:

Mapping ASP.NET SignalR Connections to Real Application Users

Briefly, you would be adding the connection ids to user on the OnConnected method and remove that connection on the OnDisconnected method. Keep in mind that an application user can have multiple connections. So, you need to have a one to many relationship between your user and the connection ids. The above linked post explains it in details with a sample.


I did this for an internal app. The way I did it is that when a user connects, I have the server ask the user to register themselves. This way I know that not only a user is connected and their signalR connnectionID, but they can also tell me any other information (like username or whatever).

When they reconnect I ask them to do it again.

SignalR will maintain the same connectionID per client even if they reconnect which is nice. A reconnection is not the same as an initial connection. New connections indicate a new client, but a reconnection is on the same client.

In my app I maintained a seperate threadsafe dictionary that I kept track of which user and which connectionID was doing what. This way I can say "oh, send message to user ABC" and look up their connectionID. Then act on the Hub's clients object in signalR for that connectionID. If you do it this way you can even have the same "user" in mutliple connections. Imagine user "abc" is open in two browser tabs. If you went strictly by connectionID they'd be technically two different users. But, by maintaining some sort of local collection grouping users and connections you can now have multiple connections for the same user.

I should mention that if you do it this way, you should make sure your site handles what happens when it restarts and loses all the connection information. For me, when someone reconnects I ask them to again re-identify themselves. This way I can re-build my local dictionary when the server comes online without worry. It does have more overhead because now you are asking all your clients to send information to you, but depending on your user case this could be staggered or bunched or otherwise distributed to help you handle load.

In general, however you get local information (whether by asking the user to supply it), or by http context session info, you need to track it yourself.


Well I used a different approach, I extended the ApplicationUser class like that:

    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.public class ApplicationUser : IdentityUser{    //public int ApplicationUserId { get; set; }    //public string Name { get; set; }    //public string Address { get; set; }    //public string City { get; set; }    //public string State { get; set; }    //public string Zip { get; set; }    [Required]    public string Email { get; set; }    [Required]    public override string UserName { get; set; }    [NotMapped]    public string ConnectionId { get; set; }    [NotMapped]    public string ChattingUserConnectionId { get; set; }    //public string HomeTown { get; set; }    //public DateTime? BirthDate { get; set; }}

And in my hub i'm doing something like that:

public class ChatHub : Hub{    #region Data Members    private static ApplicationDbContext applicationDbContext = new ApplicationDbContext();    private static UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(applicationDbContext));    private static List<ApplicationUser> connectedUsers = new List<ApplicationUser>();

And when a user connects to the chat, I get his ApplicationUser object by his user name and add it to the connectedUsers list. When he disconnects, I remove him.

I ran into some random exceptions with EF states and such which made me create the ApplicationDbContext and UserManager each time it is accessed instead of setting it in a static object:

 private ApplicationUser GetCurrentUser()    {        ApplicationDbContext applicationDbContext = new ApplicationDbContext();        UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(applicationDbContext));        var userName = Context.User.Identity.GetUserName();        var user = userManager.FindByName<ApplicationUser>(userName);        return user;    }

Edit:

The hub code has some problems loading child objects of the user. This code which is also used in the asp.net template will work better, ApplicationDBContext is not needed:

    private ApplicationUserManager _userManager    {        get        {            return HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();        }    }    var user = _userManager.FindByName<ApplicationUser, string>(userName);