Initial commit.
This commit is contained in:
18
Shared/SignalR/ClientTracker.cs
Normal file
18
Shared/SignalR/ClientTracker.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ClientApiPoC.Shared.SignalR {
|
||||
public class ClientTracker {
|
||||
private ConcurrentDictionary<string, string> _knownClientIds = new();
|
||||
|
||||
public ICollection<string> CurrentClientIds => _knownClientIds.Values;
|
||||
|
||||
public bool TryAddClientId(string clientId) {
|
||||
if (string.IsNullOrWhiteSpace(clientId)) throw new ArgumentNullException(nameof(clientId));
|
||||
return _knownClientIds.TryAdd(clientId, clientId);
|
||||
}
|
||||
|
||||
public bool TryRemoveClientId(string clientId) {
|
||||
return _knownClientIds.TryRemove(clientId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Shared/SignalR/TunnelClient.cs
Normal file
84
Shared/SignalR/TunnelClient.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace ClientApiPoC.Shared.SignalR {
|
||||
public class TunnelClient : NotifyPropertyChangedBase, IDisposable, IAsyncDisposable {
|
||||
#region IDisposable-Implementierung
|
||||
private bool _disposed;
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
if (!_disposed) {
|
||||
if (disposing) {
|
||||
_ = this.DisposeAsync();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
await DisconnectAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
private HubConnection? _connection = null;
|
||||
|
||||
public bool IsConnected => (this.ConnectionState == HubConnectionState.Connected);
|
||||
|
||||
public bool IsConnecting => (this.ConnectionState == HubConnectionState.Connecting || this.ConnectionState == HubConnectionState.Reconnecting);
|
||||
|
||||
protected HubConnectionState ConnectionState => (_connection?.State ?? HubConnectionState.Disconnected);
|
||||
|
||||
public async Task ConnectAsync(string url, Action<HubConnection> configureConnection) {
|
||||
try {
|
||||
if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url));
|
||||
if (this.IsConnected) throw new InvalidOperationException("SignalR connection is already established.");
|
||||
_connection = new HubConnectionBuilder().WithUrl(url).WithAutomaticReconnect().Build();
|
||||
_connection.Reconnecting += Connection_Reconnecting;
|
||||
_connection.Reconnected += Connection_Reconnected;
|
||||
_connection.Closed += Connection_Closed;
|
||||
configureConnection(_connection);
|
||||
await _connection.StartAsync();
|
||||
} finally {
|
||||
await UpdateStateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync() {
|
||||
try {
|
||||
if (_connection == null) return;
|
||||
try {
|
||||
if (this.IsConnected) await _connection.StopAsync();
|
||||
} catch { }
|
||||
_connection.Reconnecting -= Connection_Reconnecting;
|
||||
_connection.Reconnected -= Connection_Reconnected;
|
||||
_connection.Closed -= Connection_Closed;
|
||||
await _connection.DisposeAsync();
|
||||
_connection = null;
|
||||
} finally {
|
||||
await UpdateStateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Connection_Reconnecting(Exception? arg) {
|
||||
await UpdateStateAsync();
|
||||
}
|
||||
|
||||
private async Task Connection_Reconnected(string? arg) {
|
||||
await UpdateStateAsync();
|
||||
}
|
||||
|
||||
private async Task Connection_Closed(Exception? arg) {
|
||||
await UpdateStateAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateStateAsync() {
|
||||
OnPropertyChanged(nameof(IsConnected));
|
||||
OnPropertyChanged(nameof(IsConnecting));
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Shared/SignalR/TunnelHub.cs
Normal file
20
Shared/SignalR/TunnelHub.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ClientApiPoC.Shared.SignalR {
|
||||
public sealed class TunnelHub : Hub {
|
||||
private ClientTracker _clientTracker;
|
||||
|
||||
public TunnelHub(ClientTracker clientTracker) : base() {
|
||||
if (clientTracker == null) throw new ArgumentNullException(nameof(clientTracker));
|
||||
_clientTracker = clientTracker;
|
||||
}
|
||||
|
||||
public override sealed async Task OnConnectedAsync() {
|
||||
_ = _clientTracker.TryAddClientId(this.Context.ConnectionId);
|
||||
}
|
||||
|
||||
public override sealed async Task OnDisconnectedAsync(Exception exception) {
|
||||
_ = _clientTracker.TryRemoveClientId(this.Context.ConnectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Shared/SignalR/TunnelHubExtensions.cs
Normal file
21
Shared/SignalR/TunnelHubExtensions.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ClientApiPoC.Shared.SignalR {
|
||||
public static class TunnelHubExtensions {
|
||||
public static IServiceCollection AddTunnelHub(this IServiceCollection services) {
|
||||
services.AddSignalR(options => {
|
||||
// Nachrichtengröße auf max. 16MB bringen (Standard = 32KB).
|
||||
options.MaximumReceiveMessageSize = (16 * 1024 * 1024);
|
||||
});
|
||||
services.AddSingleton<ClientTracker>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static HubEndpointConventionBuilder MapTunnelHub(this IEndpointRouteBuilder app, [StringSyntax("Route")] string pattern) {
|
||||
return app.MapHub<TunnelHub>(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Shared/SignalR/TunnelServerBase.cs
Normal file
36
Shared/SignalR/TunnelServerBase.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ClientApiPoC.Shared.SignalR {
|
||||
public abstract class TunnelServerBase {
|
||||
protected IHubContext<TunnelHub> HubContext { get; private set; }
|
||||
|
||||
protected ClientTracker Clients { get; private set; }
|
||||
|
||||
public TunnelServerBase(IHubContext<TunnelHub> hubContext, ClientTracker clientTracker) {
|
||||
if (hubContext == null) throw new ArgumentNullException(nameof(hubContext));
|
||||
if (clientTracker == null) throw new ArgumentNullException(nameof(clientTracker));
|
||||
this.HubContext = hubContext;
|
||||
this.Clients = clientTracker;
|
||||
}
|
||||
|
||||
protected ISingleClientProxy? TryGetClient(string clientId) {
|
||||
ISingleClientProxy? client;
|
||||
try {
|
||||
client = this.HubContext.Clients.Client(clientId);
|
||||
} catch {
|
||||
client = null;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
protected IDictionary<string, ISingleClientProxy> GetAllClients() {
|
||||
var results = new Dictionary<string, ISingleClientProxy>();
|
||||
var allClientIds = this.Clients.CurrentClientIds;
|
||||
foreach (var clientId in allClientIds) {
|
||||
var client = TryGetClient(clientId);
|
||||
if (client != null) results.Add(clientId, client);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user