Initial commit.
This commit is contained in:
9
Shared/Models/ClientDataModel.cs
Normal file
9
Shared/Models/ClientDataModel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ClientApiPoC.Shared.Models {
|
||||
public class ClientDataModel {
|
||||
public string? Data { get; set; } = null;
|
||||
|
||||
public DateTime TimestampClientUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime TimestampServerUtc { get; set; } = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
7
Shared/Models/ClientResultModel.cs
Normal file
7
Shared/Models/ClientResultModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ClientApiPoC.Shared.Models {
|
||||
public class ClientResultModel {
|
||||
public string ClientId { get; set; } = "";
|
||||
|
||||
public ClientDataModel? ClientData { get; set; } = null;
|
||||
}
|
||||
}
|
||||
7
Shared/Models/ExampleRequestResultModel.cs
Normal file
7
Shared/Models/ExampleRequestResultModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ClientApiPoC.Shared.Models {
|
||||
public class ExampleRequestResultModel {
|
||||
public string? ServerMessage { get; set; }
|
||||
|
||||
public IEnumerable<ClientResultModel> Clients { get; set; } = [];
|
||||
}
|
||||
}
|
||||
12
Shared/NotifyPropertyChangedBase.cs
Normal file
12
Shared/NotifyPropertyChangedBase.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ClientApiPoC.Shared {
|
||||
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged {
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Shared/Routes.cs
Normal file
9
Shared/Routes.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ClientApiPoC.Shared {
|
||||
public static class Routes {
|
||||
private const string BASE_PATH = "/api/";
|
||||
|
||||
public const string TUNNEL_PATH = "/tunnel";
|
||||
|
||||
public const string EXAMPLE_REQUEST = $"{BASE_PATH}ExampleRequest";
|
||||
}
|
||||
}
|
||||
23
Shared/Shared.csproj
Normal file
23
Shared/Shared.csproj
Normal file
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>ClientApiPoC.Shared</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DebugType>portable</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Shared/Tools.cs
Normal file
15
Shared/Tools.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Net.Http;
|
||||
|
||||
namespace ClientApiPoC.Shared {
|
||||
public static class Tools {
|
||||
public static HttpClient CreateHttpClient() {
|
||||
var httpClientHandler = new HttpClientHandler() {
|
||||
ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => {
|
||||
// HACK: Allen SSL-Zertifikaten vertrauen!
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return new HttpClient(httpClientHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user