3
votes

In my application I need to connect to signalR hub from one page and then I want to redirect to another page and still use the same signalR connection to interact with server. If I connect to signalR hub from one page, Page1, and redirect to another page the chat objects from Page1 are not valid...

***var chat = $.connection.chatHub;
// Start the connection.
$.connection.hub.start().done(function () {});***

I also tried to save the chat variable to sessionStorage in the page that create the connection to signalR hub and then restore it in the other page but no success...

Is there a way to connect with signalR hub from Page1 and use the same connection from another page (use the same connectionID) ?

1
Thank you Nips, I connected from other page and got the same connection ID.Shai
I guess I was wrong, after the initial connection from Page1.htm I redirect the user to Page2 and reconnect to signalR hub and I got different connectionID.Shai

1 Answers

2
votes

So if I am understanding your question right then you want to be able to keep the same user connected to your signalr hub when they navigate to anther page. If so then I came up with a solution that has been working really well for me.

Here are the methods in the hub class:

private static List<UserViewModel> ConnectedUsers = new List<UserViewModel>();


/*
 * The 4 methods below handle user connection/disconnection
 */
public void Connect(string UserId)
{
    if(ConnectedUsers.Count(x => x.ConnectionId.Equals(Context.ConnectionId)) == 0)
    {
        if(ConnectedUsers.Count(x => x.UserId.Equals(UserId)) == 0)
        {
            var ConnectionId = Context.ConnectionId;
            ConnectedUsers.Add(new UserViewModel(UserId, ConnectionId));
        }
        else
        {
            SetConnection(UserId);
        }
    }
}

//This will be called when a user disconnects
public void Disconnect()
{
    var item = ConnectedUsers.FirstOrDefault(x => x.ConnectionId.Equals(Context.ConnectionId));
    if(item != null)
    {
        ConnectedUsers.Remove(item);
        //Update 
    }
}

//This method will handle when a user is assigned another ConnectionId
public void SetConnection(string UserId)
{
    if (ConnectedUsers.Count(x => x.UserId.Equals(UserId)) != 0)
    {
        UserViewModel Existing = ConnectedUsers.FirstOrDefault(x => x.UserId.Equals(UserId));
        ConnectedUsers.Remove(Existing);
        Existing.ConnectionId = Context.ConnectionId;
        ConnectedUsers.Add(Existing);
    }
    else
    {
        Connect(UserId);
    }
}

/*
This gets called every time a user navigates to another page, 
but if they stay on the site then their connection will be reset 
and if they go off site then their connection will be terminated and reset
the next time the log back into the site
*/
public override Task OnDisconnected(bool stopCalled)
{
    Disconnect();
    return base.OnDisconnected(stopCalled);
}

In your application you need to assign your users a unique and static ID or GUID that can be paired with a ViewModel:

public class UserViewModel
{
    public string UserId { get; set; }
    public string ConnectionId { get; set; }

    public UserViewModel() { }
    public UserViewModel(string UserId, string ConnectionId)
    {
        this.UserId = UserId;
        this.ConnectionId = ConnectionId;
    }
}

The connection method will handle when a user connects. First it will look to see if a connected user is already contained in the ConnectedUsers list and it does that by looking up a user by their ConnectionId and their UserId. If they do not exist then they are added to the list, but if they do exist then we go to the SetConnection method.

In the SetConnection method we get a UserId and we lookup to make sure that there is an existing user in the list. If we find an existing user then we remove them from the list, update their connection id, and then put them back in the list. This helps make sure that the Count has the right number of users. You will notice that in the SetConnection method there is a call to Connect and I will explain that a little later on.

Next is the Disconnect method. It will look for a User in the ConnectedUser list by their UserId (which is important because that is the one that will always stay the same) and remove them. This method will be called when a user logs out OR by our OnDisconnect method.

The OnDisconnect method is one that you can see I have overridden. This gets called whenever a user navigates to another page on your site or leaves the site entirely (More on this later).

Now we can examine our JS:

$(document).ready(function () {
    var shub = $.connection.securityHub;
    $.connection.hub.start().done(function () {
        Connect(shub);
    });

    //Client calls here
});

//Server calls here

function Connect(shub) {
    var id = $('#unique-user-id').attr('data-uid');

    if (Cookies.get('Connected') == null) {
        Cookies.set('Connected', 'UserConnected', { path: '' });
        shub.server.connect(id);;
    }
    else {
        shub.server.setConnection(id);
    }
}

function Disconnect() {
    Cookies.remove('Connected', { path: '' });
    $.connection.securityHub.server.disconnect();
}

Make sure that a script tag is added to your master page or _Layout.cshtml with a reference to this because this will need to run on every page of your site. In the $(document).ready part it is going to start the hub every time a page is loaded which will also call our connect method outside of the $(document).ready.

The Connect method takes the hub as an argument and it will find the unique ID that I have set on the page ($('#unique-user-id') is an element in my _Layout so every page will have it, but you can pass the UserId any way you want). Then it will look for a cookie I call Connected. This cookie expires when the session expires so if they were to close the tab or browser then the Connected cookie will go away. If the cookie is null then I set the cookie and run the Connect method that is in the Hub.cs. If the cookie already exists then we run the SetConnection method instead so that an existing user can get a new ConnectionId.

I have included a Disconnect method that can be fired whenever you want. Once your logout button has been clicked then you can run the disconnect method and it will delete the cookie and disconnect you from the server.

Now is when I would like to talk about the Connect and OnDisconnect methods. This is a method that runs EVERY time a user navigates to another page on your site, closes their browser, or exits the tab with your site. Which means that every time a user navigates to another page on your site first they will be disconnected (but their Connected cookie will remain in tact because if they go to another page none of our JS methods have been called so we didn't delete their cookie). Once the page loads our JS comes into play and the $(document).ready runs and then runs. It will start the hub and then run the JS Connect method which will see that the Connect cookie exists and run the SetConnection method on the server which will then find the currently connected user and give them a new ConnectionId in our list.

The only funky part of this is that if you display a list of connected users they will flicker when they go to other pages. What I mean by that is lets say you just display the user count (ConnectedUsers.Count) in a div or something. Lets say you have 5 connected users, one of those users navigates to another page the count will briefly go to 4 (because the OnDisconnect method will run) and then go back up to 5 (because the JS calls the Connect method).

One thing that you can do differently is instead of passing a UserId through JS like I am, in the Hub.cs class you can just get the UserId like so: Context.User.Identity.GetUserId() which would probably be better than having that as a data attribute on the page. If you want to do that then make sure that you include this using: using Microsoft.AspNet.Identity. This was just the way that I decided to do it.