From 5d32407f24843dda2bae96d26c9a8f47248870d8 Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Mon, 1 May 2017 10:53:07 -0700 Subject: [PATCH] More structural way of storing users --- samples/ChatSample/ChatSample.csproj | 1 + samples/ChatSample/RedisUserTracker.cs | 72 ++++++++++++---------- samples/ChatSample/Startup.cs | 10 ++- samples/ChatSample/Views/Home/Index.cshtml | 1 - 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/samples/ChatSample/ChatSample.csproj b/samples/ChatSample/ChatSample.csproj index 8c6f0ddf1d..30024a8cc9 100644 --- a/samples/ChatSample/ChatSample.csproj +++ b/samples/ChatSample/ChatSample.csproj @@ -41,6 +41,7 @@ + diff --git a/samples/ChatSample/RedisUserTracker.cs b/samples/ChatSample/RedisUserTracker.cs index e70de95161..ec55b6bf58 100644 --- a/samples/ChatSample/RedisUserTracker.cs +++ b/samples/ChatSample/RedisUserTracker.cs @@ -13,18 +13,23 @@ using Microsoft.AspNetCore.SignalR.Redis; using Microsoft.AspNetCore.Sockets; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using StackExchange.Redis; namespace ChatSample { public class RedisUserTracker : IUserTracker { - private readonly RedisKey UsersOnlineRedisKey = "UsersOnline"; + private readonly RedisKey UserIndexRedisKey = "UserIndex"; private readonly int _redisDatabase; private readonly ConnectionMultiplexer _redisConnection; private readonly ISubscriber _redisSubscriber; private readonly ILogger _logger; - private readonly RedisChannel _redisChannel; + + private const string UserAddedChannelName = "UserAdded"; + private const string UserRemovedChannelName = "UserRemoved"; + private readonly RedisChannel _userAddedChannel; + private readonly RedisChannel _userRemovedChannel; public event Action UserJoined; public event Action UserLeft; @@ -35,21 +40,11 @@ namespace ChatSample _redisDatabase = options.Value.Options.DefaultDatabase.GetValueOrDefault(); _redisConnection = ConnectToRedis(options.Value, _logger); _redisSubscriber = _redisConnection.GetSubscriber(); - _redisChannel = new RedisChannel((string)UsersOnlineRedisKey, RedisChannel.PatternMode.Literal); - _redisSubscriber.Subscribe(_redisChannel, (channel, value) => - { - var stringValue = (string)value; - var user = ToUserDetails(stringValue.Substring(1)); - if (stringValue[0] == '-') - { - UserLeft(user); - } - else - { - UserJoined(user); - } - }); + _userAddedChannel = new RedisChannel(UserAddedChannelName, RedisChannel.PatternMode.Literal); + _userRemovedChannel = new RedisChannel(UserRemovedChannelName, RedisChannel.PatternMode.Literal); + _redisSubscriber.Subscribe(_userAddedChannel, (channel, value) => UserJoined(DeserializerUser(value))); + _redisSubscriber.Subscribe(_userRemovedChannel, (channel, value) => UserLeft(DeserializerUser(value))); } private static ConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger) @@ -71,37 +66,48 @@ namespace ChatSample return ConnectionMultiplexer.Connect(configurationOptions, loggerTextWriter); } - private static UserDetails ToUserDetails(string user) - { - var pos = user.IndexOf("|"); - Debug.Assert(pos >= 0, "Invalid user details format"); - return new UserDetails(user.Substring(0, pos), user.Substring(pos + 1)); - } - public async Task AddUser(Connection connection, UserDetails userDetails) { var database = _redisConnection.GetDatabase(_redisDatabase); - var user = $"{connection.ConnectionId}|{connection.User.Identity.Name}"; - + var key = GetUserRedisKey(connection); + var user = SerializeUser(connection); // need to await to make sure user is added before we call into the Hub - await database.SetAddAsync(UsersOnlineRedisKey, $"{connection.ConnectionId}|{connection.User.Identity.Name}"); - _ = _redisSubscriber.PublishAsync(_redisChannel, "+" + user); + await database.StringSetAsync(key, SerializeUser(connection)); + await database.SetAddAsync(UserIndexRedisKey, key); + _ = _redisSubscriber.PublishAsync(_userAddedChannel, user); } public async Task RemoveUser(Connection connection) { var database = _redisConnection.GetDatabase(_redisDatabase); - var user = $"{connection.ConnectionId}|{connection.User.Identity.Name}"; - - await database.SetRemoveAsync(UsersOnlineRedisKey, user); - _ = _redisSubscriber.PublishAsync(_redisChannel, "-" + user); + await database.SetRemoveAsync(UserIndexRedisKey, connection.ConnectionId); + if (await database.KeyDeleteAsync(GetUserRedisKey(connection))) + { + _ = _redisSubscriber.PublishAsync(_userRemovedChannel, SerializeUser(connection)); + } } public async Task> UsersOnline() { var database = _redisConnection.GetDatabase(_redisDatabase); - var usersOnline = await database.SetMembersAsync(UsersOnlineRedisKey); - return usersOnline.Select(u => ToUserDetails(u)); + var userIds = await database.SetMembersAsync(UserIndexRedisKey); + var users = await database.StringGetAsync(userIds.Select(id => (RedisKey)(string)id).ToArray()); + return users.Select(user => DeserializerUser(user)); + } + + private static string GetUserRedisKey(Connection connection) + { + return $"user:{connection.ConnectionId}"; + } + + private static string SerializeUser(Connection connection) + { + return $"{{ \"ConnectionID\": \"{connection.ConnectionId}\", \"Name\": \"{connection.User.Identity.Name}\" }}"; + } + + private static UserDetails DeserializerUser(string userJson) + { + return JsonConvert.DeserializeObject(userJson); } private class LoggerTextWriter : TextWriter diff --git a/samples/ChatSample/Startup.cs b/samples/ChatSample/Startup.cs index 00f2844077..86ff669993 100644 --- a/samples/ChatSample/Startup.cs +++ b/samples/ChatSample/Startup.cs @@ -56,20 +56,18 @@ namespace ChatSample // To use Redis scaleout uncomment .AddRedis and uncomment Redis related lines below for presence services.AddSignalR() - // .AddRedis() + .AddRedis() ; services.AddAuthentication(); - services.AddSingleton(typeof(DefaultHubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>)); - services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultPresenceHublifetimeMenager<>)); - services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>)); + //services.AddSingleton(typeof(DefaultHubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>)); + //services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultPresenceHublifetimeMenager<>)); + //services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>)); - /* services.AddSingleton(typeof(RedisHubLifetimeManager<>), typeof(RedisHubLifetimeManager<>)); services.AddSingleton(typeof(HubLifetimeManager<>), typeof(RedisPresenceHublifetimeMenager<>)); services.AddSingleton(typeof(IUserTracker<>), typeof(RedisUserTracker<>)); - */ } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/samples/ChatSample/Views/Home/Index.cshtml b/samples/ChatSample/Views/Home/Index.cshtml index aa2ce7c793..89b01f6800 100644 --- a/samples/ChatSample/Views/Home/Index.cshtml +++ b/samples/ChatSample/Views/Home/Index.cshtml @@ -19,7 +19,6 @@ let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets; let connection = new signalR.HubConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text'); -// connection.on('Send', message => appendLine(message)); connection.onClosed = e => { if (e) { appendLine('Connection closed with error: ' + e, 'red');