0

I'm working on a .net 6.0 project using SignalR to send notifications to users. I wanna store the notifications from users who were disconnected and re-send the notifications to these reconnected users. I'd like to use memory storage for all this. I can't find information and documentation about that.

This is my Hub:

  public class NotificationHub : Hub<INotificationHub>
     {
    
         private readonly IUserConnectionManager _userConnectionManager;
    
         public NotificationHub(IUserConnectionManager userConnectionManager)
         {
             _userConnectionManager = userConnectionManager;
         }
    
         private readonly Dictionary<string, string> _userConnections = new Dictionary<string, string>();
    
         public override async Task OnConnectedAsync()
         {
             var token = Context.GetHttpContext().Request.Headers["access_token"];
    
             if (!string.IsNullOrEmpty(token))
             {
                 bool successfulConnection = UserConnection(token);
                 if (successfulConnection)
                 {
                     var response = new ServerResponse
                     {
                         status = "Accepted",
                     };
                     await Clients.Client(Context.ConnectionId).ConnectionResponse(response);
                 }
             }
             else
             {
                 var response = new ServerResponse
                 {
                     status = "Error",
                     message = "Token no proporcionado"
                 };
                 await Clients.Client(Context.ConnectionId).ConnectionResponse(response);
                 Context.Abort();
             }
         }
    
         private bool UserConnection(string token)
         {
             var handler = new JwtSecurityTokenHandler();
             var jsonToken = handler.ReadToken(token) as JwtSecurityToken;
    
             if (jsonToken != null)
             {
                 string userId = jsonToken.Claims.FirstOrDefault(claim => claim.Type == "email")?.Value;
                 if (!string.IsNullOrEmpty(userId))
                 {
                     GetConnectionId(userId);
                 }
                 var response = new ServerResponse
                 {
                     status = "Accepted",
                 };
                 return true;
             }
             return false;
         }
    
         public string GetConnectionId(string userId)
         {
             _userConnectionManager.KeppUserConnection(userId, Context.ConnectionId);
             return Context.ConnectionId;
         }
    
         public override async Task OnDisconnectedAsync(Exception? exception)
         {
             var connectionId = Context.ConnectionId;
             _userConnectionManager.RemoveUserConnection(connectionId);
             await base.OnDisconnectedAsync(exception);
         }
    
         public async Task MessageCheck(NotificationRequestModel request)
         {
             //meter validaciones
             switch (request.Target.Device)
             {
                 case "Admin":
                     if (request.Target.User.Contains("*"))
                     {
                         SendNotificationToAllUsers(request.Notification);
                     }
                     else
                     {
                         SendNotificationToSpecificUser(request.Notification, request.Target.User);
                     }
                     break;
             }
         }
    
         public async Task SendNotificationToAllUsers(NotificationModel message)
         {
             await Clients.All.SendNotificationToAllUsers(message);
         }
    
         public async Task SendNotificationToSpecificUser(NotificationModel message, List<string> target)
         {
             foreach (var userId in target)
             {
                 var connectionIds = _userConnectionManager.GetUserConnections(userId);
                 foreach (var connectionId in connectionIds)
                 {
                     await Clients.Client(connectionId).SendNotification(message);
                 }
             }
         }
2
  • 1
    Hi Eliass, You can take a look at the GetUserIdFromToken method I answered earlier, it looks like it's more than jsonToken.Claims.FirstOrDefault(claim => claim. Type == "email")?. Value;' more convenient. Commented Feb 9, 2024 at 9:47
  • 1
    Any way, glad hear you can fix the previous issue, and I hope the answer below could help you. Commented Feb 9, 2024 at 9:48

1 Answer 1

1

You can implement this requirement by following code.

enter image description here

InMemoryNotificationStore.cs

using ASPNETCORE8.Models;

namespace ASPNETCORE8.IServices
{
    public class InMemoryNotificationStore : INotificationStore
    {
        private readonly Dictionary<string, List<NotificationModel>> _notifications = new();

        public void StoreNotification(string userId, NotificationModel notification)
        {
            if (!_notifications.ContainsKey(userId))
            {
                _notifications[userId] = new List<NotificationModel>();
            }
            _notifications[userId].Add(notification);
        }

        public List<NotificationModel> RetrieveNotifications(string userId)
        {
            if (_notifications.ContainsKey(userId))
            {
                return _notifications[userId];
            }
            return new List<NotificationModel>();
        }

        public void ClearNotifications(string userId)
        {
            if (_notifications.ContainsKey(userId))
            {
                _notifications.Remove(userId);
            }
        }
    }
}

INotificationStore.cs

using ASPNETCORE8.Models;

namespace ASPNETCORE8.IServices
{
    public interface INotificationStore
    {
        void StoreNotification(string userId, NotificationModel notification);
        List<NotificationModel> RetrieveNotifications(string userId);
        void ClearNotifications(string userId);
    }

}

And this is my NotificationHub.cs

using ASPNETCORE8.IServices;
using ASPNETCORE8.Models;
using Microsoft.AspNetCore.SignalR;
using System.Diagnostics;

namespace ASPNETCORE8.Hubs
{
    public class NotificationHub : Hub<INotificationHub>
    {
        private readonly IUserConnectionManager _userConnectionManager;
        private readonly INotificationStore _notificationStore;

        public NotificationHub(IUserConnectionManager userConnectionManager, INotificationStore notificationStore)
        {
            _userConnectionManager = userConnectionManager;
            _notificationStore = notificationStore;
        }

        public override async Task OnConnectedAsync()
        {
            var token = Context.GetHttpContext().Request.Headers["access_token"];
            // the logic of checking your token

            // checking the notifications once connected
            var userId = "testid";
            // get the messages from memory 
            var notifications = _notificationStore.RetrieveNotifications(userId);
            foreach (var notification in notifications)
            {
                await Clients.Client(Context.ConnectionId).SendNotification(notification);
            }
            _notificationStore.ClearNotifications(userId);

            await base.OnConnectedAsync();
        }

        public async Task SendNotificationToSpecificUser(NotificationModel message, List<string> target)
        {
            foreach (var userId in target)
            {
                var connectionIds = _userConnectionManager.GetConnectionsByUserid(userId);
                // check the user is online or not
                if (connectionIds.Count > 0)
                {
                    foreach (var connectionId in connectionIds)
                    {
                        await Clients.Client(connectionId).SendNotification(message);
                    }
                }
                else
                {
                    // if user is offline, then save the notification messages
                    _notificationStore.StoreNotification(userId, message);
                }
            }
        }
    }
}

Other files, INotificationHub.cs

using ASPNETCORE8.Models;

namespace ASPNETCORE8.IServices
{
    public interface INotificationHub
    {
        Task SendNotification(NotificationModel notification);
        Task ConnectionResponse(ServerResponse response);
    }

}

IUserConnectionManager.cs

namespace ASPNETCORE8.IServices
{
    public interface IUserConnectionManager
    {
        void AddConnection(string userId, string connectionId);
        void RemoveConnection(string userId, string connectionId);
        List<string> GetConnectionsByUserid(string userId);
        Dictionary<string, List<string>> GetAllConnections();
    }
}

NotificationModel.cs

namespace ASPNETCORE8.Models
{
    public class NotificationModel
    {
        public string? Title { get; set; } 
        public string? Message { get; set; } 
        public DateTime Date { get; set; } 
                                          

        public NotificationModel()
        {
        }

        public NotificationModel(string title, string message, DateTime date)
        {
            Title = title;
            Message = message;
            Date = date;
        }

    }
}

ServerResponse.cs

namespace ASPNETCORE8.Models
{
    public class ServerResponse
    {
        public string? Status { get; set; } 
        public string? Message { get; set; } 
    }

}

UserConnectionManager.cs

using ASPNETCORE8.IServices;
using System.Collections.Concurrent;

namespace ASPNETCORE8.Services
{
    public class UserConnectionManager : IUserConnectionManager
    {
        private readonly ConcurrentDictionary<string, List<string>> _connections = new ConcurrentDictionary<string, List<string>>();

        public void AddConnection(string userId, string connectionId)
        {
            _connections.AddOrUpdate(userId, new List<string> { connectionId }, (key, oldValue) =>
            {
                oldValue.Add(connectionId);
                return oldValue;
            });
        }

        public void RemoveConnection(string userId, string connectionId)
        {
            if (_connections.TryGetValue(userId, out var connections))
            {
                connections.Remove(connectionId);
                if (connections.Count == 0)
                {
                    _connections.TryRemove(userId, out _);
                }
            }
        }

        public List<string> GetConnectionsByUserid(string userId)
        {
            if (_connections.TryGetValue(userId, out var connections))
            {
                return connections;
            }
            return new List<string>();
        }
        public Dictionary<string, List<string>> GetAllConnections()
        {
            return _connections.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }
    }
}

And the settings in Program.cs.

...
builder.Services.AddSignalR();
builder.Services.AddSingleton<INotificationStore, InMemoryNotificationStore>()
...
var app = builder.Build();
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.