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.
|
||||
// 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.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ChatSample.Hubs
|
||||
{
|
||||
// TODO: Make this work
|
||||
[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()
|
||||
{
|
||||
|
|
@ -23,16 +22,18 @@ namespace ChatSample.Hubs
|
|||
Context.Connection.Dispose();
|
||||
}
|
||||
|
||||
await Clients.Client(Context.ConnectionId).InvokeAsync("SetUsersOnline", usersOnline);
|
||||
usersOnline.TryAdd(Context.ConnectionId, Context.User.Identity.Name);
|
||||
|
||||
await Clients.All.InvokeAsync("OnConnected", Context.ConnectionId, Context.User.Identity.Name);
|
||||
await Clients.Client(Context.ConnectionId).InvokeAsync("SetUsersOnline", UsersOnline);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override Task OnDisconnectedAsync(Exception ex)
|
||||
public override Task OnUserJoined(UserDetails user)
|
||||
{
|
||||
usersOnline.TryRemove(Context.ConnectionId, out var value);
|
||||
return Clients.All.InvokeAsync("OnDisconnected", Context.ConnectionId, Context.User.Identity.Name);
|
||||
return Clients.Client(Context.ConnectionId).InvokeAsync("UserJoined", user);
|
||||
}
|
||||
|
||||
public override Task OnUserLeft(UserDetails user)
|
||||
{
|
||||
return Clients.Client(Context.ConnectionId).InvokeAsync("UserLeft", user);
|
||||
}
|
||||
|
||||
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.Hosting;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -56,6 +57,8 @@ namespace ChatSample
|
|||
services.AddSignalR();
|
||||
|
||||
services.AddAuthentication();
|
||||
|
||||
services.AddSingleton<IPresenceManager, DefaultPresenceManager<Chat>>();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for (var user in usersOnline) {
|
||||
addUserOnline(user, usersOnline[user]);
|
||||
}
|
||||
usersOnline.forEach(user => addUserOnline(user));
|
||||
});
|
||||
|
||||
connection.on('OnConnected', function (id, userName) {
|
||||
|
|
@ -43,6 +41,16 @@ connection.on('OnConnected', function (id, userName) {
|
|||
connection.on('OnDisconnected', function (id, userName) {
|
||||
appendLine('User ' + userName + ' left the chat');
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Microsoft.AspNetCore.SignalR
|
|||
{
|
||||
public class Hub : Hub<IClientProxy>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class Hub<TClient> : IDisposable
|
||||
|
|
|
|||
|
|
@ -32,9 +32,14 @@ namespace Microsoft.AspNetCore.Builder
|
|||
_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);
|
||||
}
|
||||
|
||||
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.AddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
|
||||
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<IConfigureOptions<SignalROptions>, SignalROptionsSetup>();
|
||||
services.AddSingleton<JsonNetInvocationAdapter>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue