0

I'm using SignalR in a c# MVC web application. I want to have a list of objects ( UserDetail ) of the current form authenticated users. this object should hold the UserId ( Pk from table ) and ConnectionId ( SignalR ).

Today I'm using a static list in the hub class :

public static List<UserDetail> ConcurrentUsers = new List<UserDetail>();

And handle remove from list and insert to list in each of the Hub events

public override Task OnDisconnected(bool stopCalled)
{
     if (ConcurrentUsers != null && ConcurrentUsers.Any(x => x.ConnectionId.Equals(Context.ConnectionId)))
          ConcurrentUsers.Remove(ConcurrentUsers.Find(x => x.ConnectionId.Equals(Context.ConnectionId)));
     return base.OnDisconnected(stopCalled);
}

public override Task OnConnected()
{
     IsUserAuthenticated();
     return base.OnConnected();
}

public override Task OnReconnected()
{
     IsUserAuthenticated();
     return base.OnReconnected();
}

private void IsUserAuthenticated()
{
     if (HttpContext.Current != null && HttpContext.Current.User != null &&  HttpContext.Current.User.Identity.IsAuthenticated)
     {
           var user = DalHelper.GetUserNoTracking(HttpContext.Current.User.Identity.Name);
           if (user == null)
           {
                 Clients.Caller.GoToLogin();
                 return null;
           }


           if (!ConcurrentUsers.Any(x => x.ConnectionId.Equals(Context.ConnectionId)))
           {
               UserDetail ud = new UserDetail { 
                  ConnectionId = Context.ConnectionId,
                  UserId = user.UserId,
               };
               ConcurrentUsers.Add(ud);
           }

      }
      return null;
}

When I want to update a user with his data, let's say a new message he received I then can go over the list and find all the connectionId's of that user and push the message to all the connected User clients.

private static void FlushMessageToClient(Guid UserId, string Message)
{
    string[] cids = Hub.ConcurrentUsers.Where(x => x.UserId.Equals(UserId)).Select(x => x.ConnectionId).ToArray();
    foreach (string cid in cids)
    {
        Hub.Clients.Client(cid).messageReceived(Message);
    }
}

All is working good until the web site runs for an hour with in and out users ,It will start to receive errors :

System.NullReferenceException: Object reference not set to an instance of an object. at Infra.ChatHub.b__16(UserDetail x) at System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func2 predicate) at ChatHub.IsUserAuthenticated()

Is there a better way to program it ?

Haddar Macdasi
  • 3,477
  • 8
  • 37
  • 59
  • The problem is that you are updating (.Add) while itrerating the collection (.Where). You either need to use locks or change to thread safe collections – Anders Jan 16 '15 at 15:26

2 Answers2

0

I would use a dictionary if you have a key index:

Dictionary<Guid, UserDetail > dictionary = new Dictionary<Guid, UserDetail >();

You can do:

dictionary.Add(userId, userDetail);   
var detail = dictionary[userId];

About your error, check that the userId is in the list before query it.

if (dictionary.ContainsKey(userId)) {}
Eric Bole-Feysot
  • 13,949
  • 7
  • 47
  • 53
  • Please take in mind that each UserId has many ConnectionId , User can logon from different tabs / browsers . so I will need a fast reliable access to all connectionId's given UserId and vise versa. – Haddar Macdasi Jan 16 '15 at 10:24
  • Ok (I thought userId was a key, as said in question). You can then use a struct (userId, connectionID) as a key. See http://stackoverflow.com/questions/689940/hashtable-with-multidimensional-key-in-c-sharp. – Eric Bole-Feysot Jan 16 '15 at 10:47
  • UserId is key but can have more then 1 connectionid. – Haddar Macdasi Jan 16 '15 at 16:58
0

Can find the best option under Mapping SignalR Users to Connections.

http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections

Haddar Macdasi
  • 3,477
  • 8
  • 37
  • 59