Search code examples
javascriptc#asp.net-coresignalrasp.net-core-signalr

Ways to persist SignalR connection


I am creating web application, which let's users to communicate with so called chat.

In order to enable such communication, I use SignalR library. I create connection at first visit to my page (main page). So JS code, which creates connection, creates variable used to configure connection.

Then user enters chat room, which is different page, so new JSs are loaded etc. The one, which held connection variable is now unavailable. But now I need that connection to send messages in my chat room.

So this variable must be "transfered" over to next scripts.

So, what would be the way to actually persist connection through whole session on the website?


Solution

  • Finally I got my answer.

    It was suggested that I should use ASP NET Identity, which is very valid, but I already created simple authentication and users management. It isn't safe and as half good as ASP NET Identity (I looked throught it and got my head around it), but it's just personal project, not production one, so if it evolves I might switch to Identity or even implement one myself ;) But it's not the case.

    It required little bit of extra steps, so:

    1. I needed to enable sessions in ASP.NET Core, I used this article for this purpose. Having that, I can persist my user login and provide it to user ID provider for signalR

    2. Adding cutsom user ID provider for SignalR in .NET:

    I needed to create such class

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

    So, after logging I can set login in Session, so it becomes "state" variable, and use it in above class.

    Also, one need to add it in ASP services, like below (in Startup.ConfigureServices):

    services.AddSingleton<IUserIdProvider, UserIdProvider>();
    

    There also one thing, which still need to be set: in UserIdProvider we need to access Session through HttpContext. In order to use HttpContextwe need to specify it like below (also in Startup.ConfigureServices):

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    

    which passes HttpContextAccessor to serices constructors.

    After all this, you can access users in SignalR Hub with their logins, which is set in Context.UserIdnentifier

    This also enables sending messages to specific user, just by passing their login (frontend client just chooses user), like below:

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

    NOTE There was one problem though. Browsers on computer didn't persist sessions, which I solved by (also in 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;
    });
    

    Without it, you need to be carefull with cookies, if you don't accept them on a site, it won't work, as user's login won't be persisted.