SignalR C#MVC将匿名用户映射到客户端ID

时间:2016-01-21 01:45:45

标签: c# asp.net-mvc-5 signalr

问题:如何管理匿名用户,以便在Hub发出响应时,单个浏览器中的多个标签都会更新?

方案如下:

我想将SignalR集成到一个项目中,以便匿名用户可以与运营商进行实时聊天。显然,已通过iIdentity进行身份验证的用户通过Client.User(username)命令进行映射。但目前一位匿名用户正在浏览site.com/tools和site.com/notTools我无法仅使用connectionID向所有标签发送消息。只有一个选项卡收集响应。

我尝试过使用IWC补丁,但该工具并不考虑将聊天信息保存到数据库中,我认为通过ajax传递变量并不是读取/写入数据库的安全方式。

我还查看了以下内容:Managing SignalR connections for Anonymous user

然而,创建像这样的基础并使用会话似乎不如Owin安全。

我有想法通过每次用户连接时创建一个虚假帐户来使用client.user(),并在断开连接时将其删除。但我宁愿不用垃圾帐户填充我的aspnetusers数据库上下文似乎没必要。

是否可以使用UserHandler.ConnectedIds.Add(Context.ConnectionId);来映射假用户名?我不知所措。

使用iUserId提供程序是否有意义?

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

然后创建一个数据库,将IP地址存储到connectionID和单个用户名?

数据库:

用户:使用FK连接ID

|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       |

然后可能在连接周围编写逻辑?

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?
     } 
  }

但问题仍然是如何在websocket上发送命令时如何处理多个标签?

更新 为了清楚起见,我的目的是创建一个实时聊天/支持工具,允许在浏览器中始终进行匿名访问。

客户想要类似于http://www.livechatinc.com

的内容

我已经创建了一个javascript插件,可以从集线器发送和接收,无论它在哪个域上。 (我的客户有多站点)这个难题的缺失部分是最简单的,管理匿名用户以允许多标签对话。

2 个答案:

答案 0 :(得分:5)

我没有遵循虚假用户帐户的想法(不知道它是否有效),但会开发替代方案。

目标可以通过所有浏览器标签共享的ChatSessionId cookie并创建名为此ChatSessionId的组来实现。

我从asp.net/signalr获取了基本的聊天教程,并添加了允许来自同一用户的多个标签聊天的功能。

1)在" Chat"中分配聊天会话ID识别用户的操作,因为我们没有用户凭据:

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

2)进入聊天页面时订阅聊天会话

客户端

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

服务器端(集线器)

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)向用户的聊天会话广播消息

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);
}

最后,(简单)ChatSessionHelper类

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();
    }
}

答案 1 :(得分:3)

广泛采用的解决方案是让用户在onConnected上注册带有连接的某种id。

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


            return base.OnConnected();
        }

并且用户通过某种自己的id逻辑调用返回

来自客户注册方法

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

并且将用户ID保存在静态字典中(因为您需要它是线程安全的,所以请注意锁;

static readonly IConnectionCache s_ConnectionCache = new ConnectionsCache();

这里

 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);
        }



    }

示例调用注册形成一个Android应用程序

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

对于JS来说它会有点相同

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