2016-01-21 19 views
12

Domanda: Come gestisco gli utenti anonimi in modo che più schede in un singolo browser vengano tutte aggiornate quando l'hub invia una risposta?SignalR C# MVC Mapping utente anonimo a ID cliente

Lo scenario è il seguente:

Vorrei integrare SignalR in un progetto in modo che gli utenti anonimi possono live chat con gli operatori. Ovviamente gli utenti che si sono autenticati con iIdentity vengono mappati tramite il comando Client.User (username). Al momento, tuttavia, un utente anonimo sta navigando su site.com/tools e site.com/notTools Non riesco a inviare messaggi a tutte le schede con un solo ID di connessione. Solo una scheda raccoglie la risposta.

Ho provato a utilizzare la patch IWC ma quello strumento non tiene conto del salvataggio delle informazioni della chat in un database e penso che passare le variabili via ajax non sia un modo sicuro di leggere/scrivere su un database.

Ho anche guardato il seguente: Managing SignalR connections for Anonymous user

Tuttavia la creazione di una base come quella e l'utilizzo di sessioni sembra meno sicuro di Owin.

Ho avuto l'idea di utilizzare client.user() creando un account falso ogni volta che un utente si connette ed elimina quando si disconnettono. Ma preferirei non riempire il mio contesto di db aspnetusers con account spazzatura che sembra non necessario.

È possibile utilizzare UserHandler.ConnectedIds.Add(Context.ConnectionId); anche per mappare un nome utente falso? Sono in perdita.

Avrebbe senso utilizzare il provider iUserId?

public interface IUserIdProvider 
{ 
    string GetUserId(IRequest request); 
} 

Quindi creare un database che memorizza gli indirizzi IP in ID di connessione e singoli nomi utente?

database:

Utenti: Con FK agli ID di collegamento

|userid|username|IP  |connectionIDs| 
| 1 | user1 |127.0.0.1| 1   | 
| 2 | user2 |127.0.0.2| 2   | 

connectionIDs:

|connectionID|SignalRID|connectionIDs| 
|  1  | xx.x.x.x|  1  | 
|  2  | xx.xxx.x|  2  | 
|  3  | xx.xxxxx|  2  | 
|  4  | xxxxx.xx|  2  | 

poi eventualmente scrivere la logica di tutto il collegamento?

public override Task OnConnected() 
    { 
    if(Context.Identity.UserName != null){ 
     //add a connection based on the currently logged in user. 
    } 
    else{ 
    ///save new user to database? 
    } 
    } 

ma la domanda ancora rimane come avrei gestire più schede con che quando si invia un comando sul websocket?

aggiornamento Per essere chiari, il mio intento è quello di creare uno strumento di chat/supporto live che permette di browser l'accesso anonimo a tutti i tempi.

il cliente vuole qualcosa di simile a http://www.livechatinc.com

ho già creato un plugin JavaScript che invia e riceve dal mozzo, indipendentemente da quale dominio si trova. (il mio cliente ha multisiti) il pezzo mancante del puzzle è il più semplice, gestendo gli utenti anonimi per consentire conversazioni multi-tab.

+0

Cosa vuoi è un utente anonimo di essere in grado di chattare allo stesso sito (es. site.com) da qualsiasi scheda aperta del browser, giusto? – tede24

+0

@ tede24 sì è quello che vorrei. – Chris

+3

Non puoi semplicemente impostare un cookie con qualche tipo di SessionId (Guid), creare un gruppo per quell'Id e sottoscrivere tutte le connessioni lì? In questo modo potresti avere messaggi trasmessi a tutte le schede – tede24

risposta

2

una soluzione ampiamente adottata è quella di rendere registrare l'utente con la sua un qualche tipo di id con connessione indietro, sul onConnected.

public override Task OnConnected() 
     { 
      Clients.Caller.Register(); 


      return base.OnConnected(); 
     } 

e che l'utente torna con una chiamata con un qualche tipo della propria logica id

da parte dei clienti Registrati Metodo

public void Register(Guid userId) 
    { 
     s_ConnectionCache.Add(userId, Guid.Parse(Context.ConnectionId)); 
    } 

e di mantenere gli ID utente in un dizionario statico (prendersi cura delle serrature in quanto ne hai bisogno per essere thread sicuro:

static readonly IConnectionCache s_ConnectionCache = new ConnectionsCache(); 

qui

public class ConnectionsCache :IConnectionCache 
{ 
    private readonly Dictionary<Guid, UserConnections> m_UserConnections = new Dictionary<Guid, UserConnections>(); 
    private readonly Dictionary<Guid,Guid> m_ConnectionsToUsersMapping = new Dictionary<Guid, Guid>(); 
    readonly object m_UserLock = new object(); 
    readonly object m_ConnectionLock = new object(); 
    #region Public 


    public UserConnections this[Guid index] 
     => 
     m_UserConnections.ContainsKey(index) 
     ?m_UserConnections[index]:new UserConnections(); 

    public void Add(Guid userId, Guid connectionId) 
    { 
     lock (m_UserLock) 
     { 
      if (m_UserConnections.ContainsKey(userId)) 
      { 
       if (!m_UserConnections[userId].Contains(connectionId)) 
       { 

        m_UserConnections[userId].Add(connectionId); 

       } 

      } 
      else 
      { 
       m_UserConnections.Add(userId, new UserConnections() {connectionId}); 
      } 
     } 


      lock (m_ConnectionLock) 
      { 
       if (m_ConnectionsToUsersMapping.ContainsKey(connectionId)) 
       { 
        m_ConnectionsToUsersMapping[connectionId] = userId; 
       } 
       else 
       { 
         m_ConnectionsToUsersMapping.Add(connectionId, userId); 
       } 
      } 

    } 

    public void Remove(Guid connectionId) 
    { 
     lock (m_ConnectionLock) 
     { 
      if (!m_ConnectionsToUsersMapping.ContainsKey(connectionId)) 
      { 
       return; 
      } 
      var userId = m_ConnectionsToUsersMapping[connectionId]; 
      m_ConnectionsToUsersMapping.Remove(connectionId); 
      m_UserConnections[userId].Remove(connectionId); 
     } 



    } 

una chiamata di esempio per registrare il modulo un'applicazione Android

mChatHub.invoke("Register", PrefUtils.MY_USER_ID).get(); 

per JS sarebbe sorta la stessa

chat.client.register = function() { 
    chat.server.register(SOME_USER_ID); 
} 
+0

Mentre il tuo esempio funzionerebbe nel frattempo, non risolve il mio problema immediato. L'applicazione esiste già, tuttavia non possiamo gestire le schede concorrenti. Questo è l'unico problema. Non vogliamo costringere gli utenti a registrarsi per l'accesso ai nostri siti. Quando un utente si connette e STARTS vengono raccolti dati di conversazione, Nome, Email, Descrizione della query. Durante la connessione a una conversazione "registrata", altre schede che non sono "registrate", non ottengono la conversazione chat immediata. L'utente – Chris

+0

ha un soprannome - qui è il tuo token "di raggruppamento" - l'obiettivo non è quello di rendere l'utente registrato, l'obiettivo è raggruppare le sue connessioni insieme, come se tu rispondessi con Clients.All(). puoi facilmente ottenere le connessioni pertinenti usando i client dinamici = Clients.Clients (userConnections.Select (x => x.ToString()). ToList()); e poi spingere i dati solo su quelle connessioni. Sto usando la stessa cosa con il client Android, e non ho alcuna registrazione –

+0

Sto segnando la tua risposta come risposta. Funziona nel frattempo e ci crescerò sopra in un secondo momento. – Chris

5

Non sto seguendo l'idea di account utente errato (non so se funziona), ma svilupperò un'alternativa.

L'obiettivo può essere raggiunto tramite un cookie ChatSessionId condiviso da tutte le schede del browser e creando gruppi denominati come ChatSessionId.

Ho seguito il tutorial di base della chat da asp.net/signalr e ho aggiunto funzionalità per consentire la chat da più schede come lo stesso utente.

1) Assegnare un Id nelle sessioni di chat in azione "Chat" per identificare l'utente, in quanto non abbiamo credenziali utente:

public ActionResult Chat() 
    { 
     ChatSessionHelper.SetChatSessionCookie(); 
     return View(); 
    } 

2) Iscriviti a chiacchierare sessione quando accedere alla pagina di chat

lato client

$.connection.hub.start() 
     .then(function(){chat.server.joinChatSession();}) 
     .done(function() { 
      ... 

lato server (hub)

public Task JoinChatSession() 
{ 
    //get user SessionId to allow use multiple tabs 
    var sessionId = ChatSessionHelper.GetChatSessionId(); 
    if (string.IsNullOrEmpty(sessionId)) throw new InvalidOperationException("No chat session id"); 

    return Groups.Add(Context.ConnectionId, sessionId); 
} 

3) di messaggi broadcast a sessione di chat dell'utente

public void Send(string message) 
{ 
    //get user chat session id 
    var sessionId = ChatSessionHelper.GetChatSessionId(); 
    if (string.IsNullOrEmpty(sessionId)) throw new InvalidOperationException("No chat session id"); 

    //now message will appear in all tabs 
    Clients.Group(sessionId).addNewMessageToPage(message); 
} 

Infine, il semplice ChatSessionHelper() della classe

public static class ChatSessionHelper 
{ 
    public static void SetChatSessionCookie() 
    { 
     var context = HttpContext.Current; 
     HttpCookie cookie = context.Request.Cookies.Get("Session") ?? new HttpCookie("Session", GenerateChatSessionId()); 

     context.Response.Cookies.Add(cookie); 
    } 

    public static string GetChatSessionId() 
    { 
     return HttpContext.Current.Request.Cookies.Get("Session")?.Value; 
    } 

    public static string GenerateChatSessionId() 
    { 
     return Guid.NewGuid().ToString(); 
    } 
} 
+0

Mentre la tua risposta funzionerà, non rispetta ciò di cui ho bisogno, che è un sistema di chat in stile sicuro. Qualsiasi persona può falsificare i propri cookie e accedere alle chat che non erano destinati a utilizzare. Ho bisogno di una soluzione che gestisca gli ID in base a dati che non possono essere falsificati ... [email protected] tede24 – Chris

+0

Questa soluzione funzionerà per un dominio, ma non tiene conto del fatto che due domini devono condividere il cookie. – Chris

+0

@chris due domini non è indicato nella domanda, l'esempio è sempre site.com/xx. In ogni caso, puoi condividere i cookie ogni volta che ti servono con le corrette impostazioni dei cookie – tede24