Search code examples
javascriptreactjssignalrreal-timeasp.net-core-signalr

How can I store data from client's side in an SignalR real-time application?


Let's say I have an chat app. When an user logged in, I want to save their Id. Usually in a normal web app, I will save it inside javascript's localStorage. But if I use this method in my chat app, when multiple users use my app, data inside localStorage will override each other. For example, if an user named with Id 01 logged in, and then got their Id is saved by localStorage.setItem("userId"). And after that, another user with Id 11 logged in, and got their Id saved. Now, data associated with key "userId" will become 11.

Is there anyway I can store data corresponding to each user? The only way I could think of is store it on server side, when user logged in, I will have a method that stores client's connectionId in a group named by userId, and a dictionary with key is connectionId and value is userData:

        public async Task Login(int userId)
        {
            var userIdStr = userId.ToString();
            await Groups.AddToGroupAsync(Context.ConnectionId, userIdStr);
            _tempDb.connection[Context.ConnectionId] = userIdStr;
        }

        public async Task GetUserInfo()
        {
            if( _tempDb.connection.TryGetValue(Context.ConnectionId, out string userId))
            {
                await 
          Clients.Client(Context.ConnectionId).SendAsync("GetUserInfo", userId.ToString());
            }else
            {
                Console.WriteLine("ConnectionId doesnt exist");
            }
        }

But that means everytime I need info about an user, I will have to invoke this method. Is there any better way to archive this?


Solution

  • Hi @jpesa If you have authentication in your application, you can check the token and get the userid, If not use authentication, you can connect hub like below.

    var connection = new signalR.HubConnectionBuilder().withUrl("/mainHub?uid=jason&sid=test")
    .configureLogging(signalR.LogLevel.Trace).build();
    

    And get userid in OnConnectedAsync Method.

    //IHttpContextFeature? hcf = this.Context?.Features?[typeof(IHttpContextFeature)] as IHttpContextFeature;
    //HttpContext? hc = hcf?.HttpContext;
    // userid
    string? uid = Context?.GetHttpContext()?.Request.Query["uid"].ToString();
    //string? uid = hc?.Request?.Path.Value?.Split(new string[] { "=", "" }, StringSplitOptions.RemoveEmptyEntries)[1].ToString();
    // system id
    string? sid = Context.GetHttpContext()?.Request.Query["sid"].ToString();
    //string? sid = hc?.Request?.Path.Value?.Split(new string[] { "=", "" }, StringSplitOptions.RemoveEmptyEntries)[2].ToString();
    

    We can use localStorage/IndexDB to data. In your chat scenario, I prefer to store the JSON object (containing any information you want to store) in the value, and timestamp as the key.

    Here is the sample code

    function saveMessage(userId, connectionId, nickname, content) {
        const timestamp = new Date().toISOString(); 
        const key = `message_${userId}_${timestamp}`;
        const message = {
            connectionId: connectionId,
            nickname: nickname,
            content: content,
            timestamp: timestamp
        };
        localStorage.setItem(key, JSON.stringify(message));
    }
    
    function loadMessages(userId) {
        const keys = Object.keys(localStorage).filter(key => key.startsWith(`message_${userId}_`));
        keys.sort(); // sort by time
    
        const messages = keys.map(key => JSON.parse(localStorage.getItem(key)));
        return messages;
    }
    

    While we store the data locally, we can also consider storing the data to the back-end database, and the local content will be loaded first when reloading the chat message, which will reduce the pressure on the server.