More structural way of storing users
This commit is contained in:
parent
11f23f7ce2
commit
5d32407f24
|
|
@ -41,6 +41,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="CopyTSClient" BeforeTargets="AfterBuild">
|
<Target Name="CopyTSClient" BeforeTargets="AfterBuild">
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,23 @@ using Microsoft.AspNetCore.SignalR.Redis;
|
||||||
using Microsoft.AspNetCore.Sockets;
|
using Microsoft.AspNetCore.Sockets;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
|
|
||||||
namespace ChatSample
|
namespace ChatSample
|
||||||
{
|
{
|
||||||
public class RedisUserTracker<THub> : IUserTracker<THub>
|
public class RedisUserTracker<THub> : IUserTracker<THub>
|
||||||
{
|
{
|
||||||
private readonly RedisKey UsersOnlineRedisKey = "UsersOnline";
|
private readonly RedisKey UserIndexRedisKey = "UserIndex";
|
||||||
private readonly int _redisDatabase;
|
private readonly int _redisDatabase;
|
||||||
private readonly ConnectionMultiplexer _redisConnection;
|
private readonly ConnectionMultiplexer _redisConnection;
|
||||||
private readonly ISubscriber _redisSubscriber;
|
private readonly ISubscriber _redisSubscriber;
|
||||||
private readonly ILogger _logger;
|
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<UserDetails> UserJoined;
|
public event Action<UserDetails> UserJoined;
|
||||||
public event Action<UserDetails> UserLeft;
|
public event Action<UserDetails> UserLeft;
|
||||||
|
|
@ -35,21 +40,11 @@ namespace ChatSample
|
||||||
_redisDatabase = options.Value.Options.DefaultDatabase.GetValueOrDefault();
|
_redisDatabase = options.Value.Options.DefaultDatabase.GetValueOrDefault();
|
||||||
_redisConnection = ConnectToRedis(options.Value, _logger);
|
_redisConnection = ConnectToRedis(options.Value, _logger);
|
||||||
_redisSubscriber = _redisConnection.GetSubscriber();
|
_redisSubscriber = _redisConnection.GetSubscriber();
|
||||||
_redisChannel = new RedisChannel((string)UsersOnlineRedisKey, RedisChannel.PatternMode.Literal);
|
|
||||||
|
|
||||||
_redisSubscriber.Subscribe(_redisChannel, (channel, value) =>
|
_userAddedChannel = new RedisChannel(UserAddedChannelName, RedisChannel.PatternMode.Literal);
|
||||||
{
|
_userRemovedChannel = new RedisChannel(UserRemovedChannelName, RedisChannel.PatternMode.Literal);
|
||||||
var stringValue = (string)value;
|
_redisSubscriber.Subscribe(_userAddedChannel, (channel, value) => UserJoined(DeserializerUser(value)));
|
||||||
var user = ToUserDetails(stringValue.Substring(1));
|
_redisSubscriber.Subscribe(_userRemovedChannel, (channel, value) => UserLeft(DeserializerUser(value)));
|
||||||
if (stringValue[0] == '-')
|
|
||||||
{
|
|
||||||
UserLeft(user);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UserJoined(user);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger)
|
private static ConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger)
|
||||||
|
|
@ -71,37 +66,48 @@ namespace ChatSample
|
||||||
return ConnectionMultiplexer.Connect(configurationOptions, loggerTextWriter);
|
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)
|
public async Task AddUser(Connection connection, UserDetails userDetails)
|
||||||
{
|
{
|
||||||
var database = _redisConnection.GetDatabase(_redisDatabase);
|
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
|
// 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}");
|
await database.StringSetAsync(key, SerializeUser(connection));
|
||||||
_ = _redisSubscriber.PublishAsync(_redisChannel, "+" + user);
|
await database.SetAddAsync(UserIndexRedisKey, key);
|
||||||
|
_ = _redisSubscriber.PublishAsync(_userAddedChannel, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveUser(Connection connection)
|
public async Task RemoveUser(Connection connection)
|
||||||
{
|
{
|
||||||
var database = _redisConnection.GetDatabase(_redisDatabase);
|
var database = _redisConnection.GetDatabase(_redisDatabase);
|
||||||
var user = $"{connection.ConnectionId}|{connection.User.Identity.Name}";
|
await database.SetRemoveAsync(UserIndexRedisKey, connection.ConnectionId);
|
||||||
|
if (await database.KeyDeleteAsync(GetUserRedisKey(connection)))
|
||||||
await database.SetRemoveAsync(UsersOnlineRedisKey, user);
|
{
|
||||||
_ = _redisSubscriber.PublishAsync(_redisChannel, "-" + user);
|
_ = _redisSubscriber.PublishAsync(_userRemovedChannel, SerializeUser(connection));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<UserDetails>> UsersOnline()
|
public async Task<IEnumerable<UserDetails>> UsersOnline()
|
||||||
{
|
{
|
||||||
var database = _redisConnection.GetDatabase(_redisDatabase);
|
var database = _redisConnection.GetDatabase(_redisDatabase);
|
||||||
var usersOnline = await database.SetMembersAsync(UsersOnlineRedisKey);
|
var userIds = await database.SetMembersAsync(UserIndexRedisKey);
|
||||||
return usersOnline.Select(u => ToUserDetails(u));
|
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<UserDetails>(userJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoggerTextWriter : TextWriter
|
private class LoggerTextWriter : TextWriter
|
||||||
|
|
|
||||||
|
|
@ -56,20 +56,18 @@ namespace ChatSample
|
||||||
|
|
||||||
// To use Redis scaleout uncomment .AddRedis and uncomment Redis related lines below for presence
|
// To use Redis scaleout uncomment .AddRedis and uncomment Redis related lines below for presence
|
||||||
services.AddSignalR()
|
services.AddSignalR()
|
||||||
// .AddRedis()
|
.AddRedis()
|
||||||
;
|
;
|
||||||
services.AddAuthentication();
|
services.AddAuthentication();
|
||||||
|
|
||||||
|
|
||||||
services.AddSingleton(typeof(DefaultHubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
//services.AddSingleton(typeof(DefaultHubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
||||||
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultPresenceHublifetimeMenager<>));
|
//services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultPresenceHublifetimeMenager<>));
|
||||||
services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>));
|
//services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>));
|
||||||
|
|
||||||
/*
|
|
||||||
services.AddSingleton(typeof(RedisHubLifetimeManager<>), typeof(RedisHubLifetimeManager<>));
|
services.AddSingleton(typeof(RedisHubLifetimeManager<>), typeof(RedisHubLifetimeManager<>));
|
||||||
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(RedisPresenceHublifetimeMenager<>));
|
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(RedisPresenceHublifetimeMenager<>));
|
||||||
services.AddSingleton(typeof(IUserTracker<>), typeof(RedisUserTracker<>));
|
services.AddSingleton(typeof(IUserTracker<>), typeof(RedisUserTracker<>));
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
||||||
let connection = new signalR.HubConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text');
|
let connection = new signalR.HubConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text');
|
||||||
|
|
||||||
// connection.on('Send', message => appendLine(message));
|
|
||||||
connection.onClosed = e => {
|
connection.onClosed = e => {
|
||||||
if (e) {
|
if (e) {
|
||||||
appendLine('Connection closed with error: ' + e, 'red');
|
appendLine('Connection closed with error: ' + e, 'red');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue