【问题标题】:Ways to persist SignalR connection保持 SignalR 连接的方法
【发布时间】:2020-07-10 04:39:50
【问题描述】:

我正在创建网络应用程序,让用户可以通过所谓的聊天进行交流。

为了实现这种通信,我使用了 SignalR 库。我在第一次访问我的页面(主页)时创建连接。因此,创建连接的 JS 代码会创建用于配置连接的变量。

然后用户进入聊天室,这是不同的页面,因此加载了新的 JS 等。保存连接变量的那个现在不可用。但现在我需要该连接才能在我的聊天室中发送消息。

所以这个变量必须“转移”到下一个脚本。

那么,在网站上的整个会话中实际保持连接的方法是什么

【问题讨论】:

  • 每个连接点的连接都是唯一的,您不能将它们转移到新页面。查看 SignalR 中的组。登录时,根据用户 ID,而不是连接,将您的用户放入名为“Lobby”的组中。如果他们进入聊天室,即使在另一个页面上,也将他们放入名为“Chat Room xxx”的组中。只要您有一个有效的客户端方法,消息就可以发送到组,无论它们在哪个页面上。

标签: javascript c# asp.net-core signalr asp.net-core-signalr


【解决方案1】:

我终于得到了答案。

有人建议我应该使用非常有效的 ASP NET Identity,但我已经创建了简单的身份验证和用户管理。它不安全,不如 ASP NET Identity 好一半(我仔细研究了一下,并理解了它),但它只是个人项目,而不是生产项目,所以如果它发展了,我可能会切换到 Identity 甚至自己实现一个;) 但事实并非如此。

它需要一点额外的步骤,所以:

  1. 我需要在 ASP.NET Core 中启用会话,为此我使用了 this article。有了这个,我可以保留我的用户登录并将其提供给 signalR 的用户 ID 提供程序

  2. 在 .NET 中为 SignalR 添加 cutsom 用户 ID 提供程序:

我需要创建这样的类

public class UserIdProvider : IUserIdProvider
{
  public static readonly string SESSION_LOGIN_KEY = "loggedUser";
  private readonly IHttpContextAccessor _httpContextAccessor;
  
  public UserIdProvider(IHttpContextAccessor httpContextAccessor) 
  {
    _httpContextAccessor = httpContextAccessor;
  }

  public string GetUserId(HubConnectionContext connection)
  {
    var session = _httpContextAccessor.HttpContext.Session;
    session.TryGetValue(SESSION_LOGIN_KEY, out byte[] loginBA);
    if (loginBA == null)
    {
      return null;
    }

    return new string(loginBA.Select(b => (char)b).ToArray());
  }
}

所以,登录后我可以在Session 中设置登录,所以它变成“状态”变量,并在上面的类中使用它。

另外,需要在 ASP 服务中添加它,如下所示(Startup.ConfigureServices):

services.AddSingleton<IUserIdProvider, UserIdProvider>();

还有一件事,仍然需要设置:在UserIdProvider 中,我们需要通过HttpContext 访问Session。为了使用HttpContext,我们需要像下面这样指定它(也在Startup.ConfigureServices中):

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

HttpContextAccessor 传递给服务构造函数。

在这一切之后,您可以使用在 Context.UserIdnentifier 中设置的登录名访问 SignalR Hub 中的用户

这也可以向特定用户发送消息,只需传递他们的登录名(前端客户端只需选择用户),如下所示:

public async Task SendMessage(string message, string user)
{
  await Clients.User(user).SendAsync("ReceiveMessage", message).ConfigureAwait(false);
}

注意 但是有一个问题。计算机上的浏览器没有持久化会话,我解决了这个问题(也在Startup.ConfigureServices):

services.Configure<CookiePolicyOptions>(options =>
{
  // This lambda determines whether user consent for non-essential cookies is needed for a given request.
  options.CheckConsentNeeded = context => false; // <~~~~ This needs to be set to false
  options.MinimumSameSitePolicy = SameSiteMode.None;
});

没有它,您需要小心使用 cookie,如果您在网站上不接受它们,它将无法工作,因为用户的登录将不会被持久化。

【讨论】:

    【解决方案2】:

    在服务器端,您可以通过user id将消息发送给特定用户:

    var user = await _userManager.GetUserAsync(Context.User);
    
    await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
    

    在客户端,无论何时启动连接并监听集线器msg,用户都会收到消息:

    connection.on('msg', function (...data) {
        console.log('data:', data);
    });
    

    通过用户 ID 发送消息,您无需关心目标用户在哪里。


    public class ChatHub : Hub
    {
        private IUserManager<ApplicationUser> _userManager;
    
        public ChatHub(UserManager<ApplicationUser> userManager)
        {
            _userManager = userManager;
        }
    
        public async Task GetInfo()
        {
            var user = await _userManager.GetUserAsync(Context.User);
    
            await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
        }
    }
    

    【讨论】:

    • 谢谢,很有趣。但是你从哪里得到_userManager 实例?它是什么类型的?
    • @MichałTurczyn 抱歉,迟到了。我已经更新了我的答案。您可以通过集线器构造函数将_userManager 用作服务。不要忘记通过在Startup 类中调用service.AddIdentity 来启用UserManager 服务
    • 我想通了,谢谢。我创建了自己的自定义身份服务 :) 再次感谢,但我使用的是 cutsom 身份提供程序而不是 ASP 身份(我已经自己管理用户)
    • 感谢您的回答,但我还是奋力拼搏:)
    猜你喜欢
    • 1970-01-01
    • 2018-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多