Search code examples
signalrsignalr-hubsignalr.clientsignalr-2

SignalR - correct implementation of chat


I need to implement chat on my web project. How to implement it on one page - there are many articles about it. But I need to have ability : 1. Notify other users, that somebody logged to site (on any page, not only on chat page) 2. Notify other users, that somebody logout

So, I have the following code of hub:

    public void Connect()
    {
        try
        {
            var id = Context.ConnectionId;
            string username = Context.User.Identity.Name;

            var currentUser = connectedUsers.Where(p => p.Username == username).FirstOrDefault();
            if (currentUser == null)
            {
                AddNewUserToCollection();
            }
            else
            {
                // update ConnectionId for sure (connection id is changed sometimes (probably if user is logged out and login again))
                if (currentUser.ConnectionId != id)
                {
                    var companyId = _chatRepository.GetCompanyIdOfUser(username); // throws exception if companyId is null
                    Groups.Remove(currentUser.ConnectionId, companyId.ToString());
                    Groups.Add(id, companyId.ToString());
                    currentUser.ConnectionId = id;
                    //Clients.Group(companyId.ToString()).onNewUserConnected(username);
                }
            }
        }
        catch(InvalidCompanyException c_ex)
        {
            Clients.Client(Context.ConnectionId).onErrorMessage($"User '{c_ex.Username}' does not exist");
        }
    }


    public void Disconnect()
    {
        string username = Context.User.Identity.Name;
        var item = connectedUsers.Where(p => p.Username == username).FirstOrDefault();
        if (item != null)
        {
            connectedUsers.Remove(item);
            Groups.Remove(item.ConnectionId, item.CompanyID.ToString());
            Clients.Group(item.CompanyID.ToString()).onUserDisconnected(item.Username);
        }
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        var item = connectedUsers.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
        if (item != null)
        {
            connectedUsers.Remove(item);
            Groups.Remove(item.ConnectionId, item.CompanyID.ToString());
            Clients.Group(item.CompanyID.ToString()).onUserDisconnected(item.Username);
        }
        return base.OnDisconnected(stopCalled);
    }

and I added the following code to _layout.cshtml:

    <script>
        $(document).ready(function () {
            var chat = $.connection.chatHub;

            $.connection.hub.start().done(function () {
                chat.server.connect();
            });
        });
    </script>

to notify other users, that the current user is logged. But debugger says, that pair OnDisconnected/Connect is called every time, when user reload page (go thru pages) with different connectionId. When I remove this client code - that pair is not called. How to implement it correctly, to notify other users, that somebody is online, but without reconnect each time?


Solution

  • Since you have the connect() call in a razor (_layout.cshtml) page, you will effectively be disconnecting/connecting on every post back (page load).

    The scenario you're trying to use is best in a SPA (or AJAX) scenario where navigation is handled asynchronously by client side JavaScript. Your current setup is refreshing the screen, reloading the JavaScript, re-running the document ready() function with each server rendered navigation.

    Another alternative is to use the client's actual user Id, and pass that to the server.connect(id) method. Then use this user Id to track the user activity instead of the hub ConnectionId.