Single server presence
This commit is contained in:
parent
963fcd41ed
commit
632c8abf77
|
|
@ -0,0 +1,78 @@
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Sockets;
|
||||||
|
|
||||||
|
namespace ChatSample
|
||||||
|
{
|
||||||
|
// TODO: not possible to use TClient instead of (implicit) IClientProxy
|
||||||
|
// public class DefaultPresenceManager<THub> : IPresenceManager where THub : HubWithPresence<TClient>
|
||||||
|
public class DefaultPresenceManager<THub> : IPresenceManager where THub : HubWithPresence
|
||||||
|
{
|
||||||
|
private IHubContext<THub> _hubContext;
|
||||||
|
private HubLifetimeManager<THub> _lifetimeManager;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
|
||||||
|
public DefaultPresenceManager(IHubContext<THub> hubContext, HubLifetimeManager<THub> lifetimeManager, IServiceScopeFactory serviceScopeFactory)
|
||||||
|
{
|
||||||
|
_hubContext = hubContext;
|
||||||
|
_lifetimeManager = lifetimeManager;
|
||||||
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Connection, UserDetails> usersOnline
|
||||||
|
= new ConcurrentDictionary<Connection, UserDetails>();
|
||||||
|
|
||||||
|
public IEnumerable<UserDetails> UsersOnline => usersOnline.Values;
|
||||||
|
|
||||||
|
public async Task UserJoined(Connection connection)
|
||||||
|
{
|
||||||
|
// `context.User?.Identity?.Name ?? string.Empty` ?
|
||||||
|
var user = new UserDetails(connection.ConnectionId, connection.User.Identity.Name);
|
||||||
|
|
||||||
|
await Notify(hub => hub.OnUserJoined(user));
|
||||||
|
|
||||||
|
usersOnline.TryAdd(connection, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UserLeft(Connection connection)
|
||||||
|
{
|
||||||
|
usersOnline.TryRemove(connection, out UserDetails user);
|
||||||
|
|
||||||
|
await Notify(hub => hub.OnUserLeft(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Notify(Func<THub, Task> invocation)
|
||||||
|
{
|
||||||
|
foreach (var connection in usersOnline.Keys)
|
||||||
|
{
|
||||||
|
using (var scope = _serviceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub, IClientProxy>>();
|
||||||
|
var hub = hubActivator.Create();
|
||||||
|
|
||||||
|
hub.Clients = _hubContext.Clients;
|
||||||
|
hub.Context = new HubCallerContext(connection);
|
||||||
|
hub.Groups = new GroupManager<THub>(connection, _lifetimeManager);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await invocation(hub);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// TODO: log
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
hubActivator.Release(hub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ChatSample
|
||||||
|
{
|
||||||
|
public class HubWithPresence : HubWithPresence<IClientProxy>
|
||||||
|
{
|
||||||
|
public HubWithPresence(IPresenceManager presenceManager)
|
||||||
|
: base(presenceManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HubWithPresence<TClient> : Hub<TClient>
|
||||||
|
{
|
||||||
|
private IPresenceManager _presenceManager;
|
||||||
|
|
||||||
|
public HubWithPresence(IPresenceManager presenceManager)
|
||||||
|
{
|
||||||
|
_presenceManager = presenceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<UserDetails> UsersOnline
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _presenceManager.UsersOnline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnConnectedAsync()
|
||||||
|
{
|
||||||
|
_presenceManager.UserJoined(Context.Connection);
|
||||||
|
return base.OnConnectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnDisconnectedAsync(Exception exception)
|
||||||
|
{
|
||||||
|
_presenceManager.UserLeft(Context.Connection);
|
||||||
|
return base.OnDisconnectedAsync(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task OnUserJoined(UserDetails user)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task OnUserLeft(UserDetails user)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
|
|
||||||
namespace ChatSample.Hubs
|
namespace ChatSample.Hubs
|
||||||
{
|
{
|
||||||
// TODO: Make this work
|
// TODO: Make this work
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class Chat : Hub
|
public class Chat : HubWithPresence
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<string, string> usersOnline = new ConcurrentDictionary<string, string>();
|
public Chat(IPresenceManager presenceManager)
|
||||||
|
: base(presenceManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -23,16 +22,18 @@ namespace ChatSample.Hubs
|
||||||
Context.Connection.Dispose();
|
Context.Connection.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Clients.Client(Context.ConnectionId).InvokeAsync("SetUsersOnline", usersOnline);
|
await Clients.Client(Context.ConnectionId).InvokeAsync("SetUsersOnline", UsersOnline);
|
||||||
usersOnline.TryAdd(Context.ConnectionId, Context.User.Identity.Name);
|
await base.OnConnectedAsync();
|
||||||
|
|
||||||
await Clients.All.InvokeAsync("OnConnected", Context.ConnectionId, Context.User.Identity.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task OnDisconnectedAsync(Exception ex)
|
public override Task OnUserJoined(UserDetails user)
|
||||||
{
|
{
|
||||||
usersOnline.TryRemove(Context.ConnectionId, out var value);
|
return Clients.Client(Context.ConnectionId).InvokeAsync("UserJoined", user);
|
||||||
return Clients.All.InvokeAsync("OnDisconnected", Context.ConnectionId, Context.User.Identity.Name);
|
}
|
||||||
|
|
||||||
|
public override Task OnUserLeft(UserDetails user)
|
||||||
|
{
|
||||||
|
return Clients.Client(Context.ConnectionId).InvokeAsync("UserLeft", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Send(string message)
|
public async Task Send(string message)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.AspNetCore.Sockets;
|
||||||
|
|
||||||
|
namespace ChatSample
|
||||||
|
{
|
||||||
|
public class UserDetails
|
||||||
|
{
|
||||||
|
public UserDetails(string connectionId, string name)
|
||||||
|
{
|
||||||
|
ConnectionId = connectionId;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConnectionId { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPresenceManager
|
||||||
|
{
|
||||||
|
IEnumerable<UserDetails> UsersOnline { get; }
|
||||||
|
Task UserJoined(Connection connection);
|
||||||
|
Task UserLeft(Connection connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ using ChatSample.Services;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -56,6 +57,8 @@ namespace ChatSample
|
||||||
services.AddSignalR();
|
services.AddSignalR();
|
||||||
|
|
||||||
services.AddAuthentication();
|
services.AddAuthentication();
|
||||||
|
|
||||||
|
services.AddSingleton<IPresenceManager, DefaultPresenceManager<Chat>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ connection.onClosed = e => {
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.on('SetUsersOnline', function (usersOnline) {
|
connection.on('SetUsersOnline', function (usersOnline) {
|
||||||
for (var user in usersOnline) {
|
usersOnline.forEach(user => addUserOnline(user));
|
||||||
addUserOnline(user, usersOnline[user]);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('OnConnected', function (id, userName) {
|
connection.on('OnConnected', function (id, userName) {
|
||||||
|
|
@ -43,6 +41,16 @@ connection.on('OnConnected', function (id, userName) {
|
||||||
connection.on('OnDisconnected', function (id, userName) {
|
connection.on('OnDisconnected', function (id, userName) {
|
||||||
appendLine('User ' + userName + ' left the chat');
|
appendLine('User ' + userName + ' left the chat');
|
||||||
document.getElementById(id).outerHTML = '';
|
document.getElementById(id).outerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.on('UserJoined', function (user) {
|
||||||
|
appendLine('User ' + userName + ' joined the chat');
|
||||||
|
addUserOnline(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('UserLeft', function (user) {
|
||||||
|
appendLine('User ' + userName + ' left the chat');
|
||||||
|
document.getElementById(user.ConnectionId).outerHTML = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on('Send', function (userName, message) {
|
connection.on('Send', function (userName, message) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
{
|
{
|
||||||
public class Hub : Hub<IClientProxy>
|
public class Hub : Hub<IClientProxy>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Hub<TClient> : IDisposable
|
public class Hub<TClient> : IDisposable
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,14 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
_routes = routes;
|
_routes = routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MapHub<THub>(string path) where THub : Hub
|
public void MapHub<THub>(string path) where THub : Hub<IClientProxy>
|
||||||
{
|
{
|
||||||
_routes.MapEndpoint<HubEndPoint<THub>>(path);
|
_routes.MapEndpoint<HubEndPoint<THub>>(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void MapHub<THub, TClient>(string path) where THub : Hub<TClient>
|
||||||
|
{
|
||||||
|
_routes.MapEndpoint<HubEndPoint<THub, TClient>>(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.AddSockets();
|
services.AddSockets();
|
||||||
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
services.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
||||||
services.AddSingleton(typeof(IHubContext<>), typeof(HubContext<>));
|
services.AddSingleton(typeof(IHubContext<>), typeof(HubContext<>));
|
||||||
|
// TODO: this breaks because of hardcoded IClientProxy
|
||||||
|
// services.AddSingleton(typeof(IHubContext<,>), typeof(HubContext<,>));
|
||||||
services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>));
|
services.AddSingleton(typeof(HubEndPoint<>), typeof(HubEndPoint<>));
|
||||||
services.AddSingleton<IConfigureOptions<SignalROptions>, SignalROptionsSetup>();
|
services.AddSingleton<IConfigureOptions<SignalROptions>, SignalROptionsSetup>();
|
||||||
services.AddSingleton<JsonNetInvocationAdapter>();
|
services.AddSingleton<JsonNetInvocationAdapter>();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue