Add project files.

This commit is contained in:
2024-08-16 11:24:48 +02:00
parent 6e24754b23
commit 6e4501f373
22 changed files with 682 additions and 0 deletions
@@ -0,0 +1,40 @@
using AspNetCore.Extensions.Attributes;
using System.Core.Extensions;
using System.Text;
using System.Text.Json;
namespace AspNetCore.Extensions.Websockets.Converters;
public class JsonWebSocketMessageConverter<T> : WebSocketMessageConverter<T>
{
private readonly JsonSerializerOptions? jsonSerializerOptions;
[DoNotInject]
public JsonWebSocketMessageConverter()
{
}
public JsonWebSocketMessageConverter(JsonSerializerOptions options)
{
this.jsonSerializerOptions = options.ThrowIfNull();
}
public override T ConvertTo(WebSocketConverterRequest request)
{
if (request.Type != System.Net.WebSockets.WebSocketMessageType.Text)
{
throw new InvalidOperationException($"Unable to deserialize message. Message is not text");
}
var stringData = Encoding.UTF8.GetString(request.Payload!);
var objData = JsonSerializer.Deserialize<T>(stringData, this.jsonSerializerOptions);
return objData ?? throw new InvalidOperationException($"Unable to deserialize message to {typeof(T).Name}");
}
public override WebSocketConverterResponse ConvertFrom(T message)
{
var serialized = JsonSerializer.Serialize(message, this.jsonSerializerOptions);
var data = Encoding.UTF8.GetBytes(serialized);
return new WebSocketConverterResponse { EndOfMessage = true, Type = System.Net.WebSockets.WebSocketMessageType.Text, Payload = data };
}
}
@@ -0,0 +1,27 @@
using System.Text;
namespace AspNetCore.Extensions.Websockets.Converters;
public sealed class TextWebSocketMessageConverter : WebSocketMessageConverter<TextContent>
{
public override TextContent ConvertTo(WebSocketConverterRequest request)
{
if (request.Type is not System.Net.WebSockets.WebSocketMessageType.Text)
{
throw new InvalidOperationException($"Cannot parse message of type {request.Type}");
}
var message = Encoding.UTF8.GetString(request.Payload!);
return new TextContent { Text = message };
}
public override WebSocketConverterResponse ConvertFrom(TextContent message)
{
return new WebSocketConverterResponse
{
Type = System.Net.WebSockets.WebSocketMessageType.Text,
EndOfMessage = true,
Payload = Encoding.UTF8.GetBytes(message.Text ?? string.Empty)
};
}
}
@@ -0,0 +1,10 @@
using AspNetCore.Extensions.Websockets.Converters;
namespace AspNetCore.Extensions.Websockets;
[WebSocketConverter<TextWebSocketMessageConverter, TextContent>]
public sealed class TextContent
{
public string? Text { get; set; }
}
@@ -0,0 +1,13 @@
namespace AspNetCore.Extensions.Websockets;
public abstract class WebSocketConverterAttributeBase : Attribute
{
public abstract Type ConverterType { get; }
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class WebSocketConverterAttribute<TConverter, TPayload> : WebSocketConverterAttributeBase
where TConverter : WebSocketMessageConverter<TPayload>, new()
{
public override sealed Type ConverterType => typeof(TConverter);
}
@@ -0,0 +1,10 @@
using System.Net.WebSockets;
namespace AspNetCore.Extensions.Websockets;
public sealed class WebSocketConverterRequest
{
public WebSocketMessageType Type { get; set; }
public byte[]? Payload { get; set; }
}
@@ -0,0 +1,10 @@
using System.Net.WebSockets;
namespace AspNetCore.Extensions.Websockets;
public sealed class WebSocketConverterResponse
{
public WebSocketMessageType Type { get; set; }
public byte[]? Payload { get; set; }
public bool EndOfMessage { get; set; }
}
@@ -0,0 +1,24 @@
using System.Extensions;
namespace AspNetCore.Extensions.Websockets;
public abstract class WebSocketMessageConverterBase
{
public abstract object ConvertToObject(WebSocketConverterRequest request);
public abstract WebSocketConverterResponse ConvertFromObject(object message);
}
public abstract class WebSocketMessageConverter<T> : WebSocketMessageConverterBase
{
public sealed override object ConvertToObject(WebSocketConverterRequest request)
{
return ConvertTo(request)!;
}
public sealed override WebSocketConverterResponse ConvertFromObject(object message)
{
return this.ConvertFrom(message.Cast<T>());
}
public abstract T ConvertTo(WebSocketConverterRequest request);
public abstract WebSocketConverterResponse ConvertFrom(T message);
}
@@ -0,0 +1,119 @@
using AspNetCore.Extensions.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Extensions;
using System.Net.WebSockets;
namespace AspNetCore.Extensions.Websockets;
public abstract class WebSocketRouteBase
{
public HttpContext? Context { get; internal set; }
public WebSocket? WebSocket { get; internal set; }
public virtual Task SocketAccepted(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public virtual Task SocketClosed()
{
return Task.CompletedTask;
}
public abstract Task ExecuteAsync(WebSocketMessageType type, byte[] data, CancellationToken cancellationToken);
}
public abstract class WebSocketRouteBase<TReceiveType> : WebSocketRouteBase
where TReceiveType : class, new()
{
private readonly Lazy<WebSocketMessageConverterBase> converter;
public WebSocketRouteBase()
{
this.converter = new Lazy<WebSocketMessageConverterBase>(() =>
{
var attribute = typeof(TReceiveType).GetCustomAttributes(true).First(a => a is WebSocketConverterAttributeBase).Cast<WebSocketConverterAttributeBase>();
var parsedConverter = GetConverter(attribute.ConverterType, this.Context!);
return parsedConverter;
});
}
public sealed override Task ExecuteAsync(WebSocketMessageType type, byte[] data, CancellationToken cancellationToken)
{
try
{
var objData = this.converter.Value.ConvertToObject(new WebSocketConverterRequest { Type = type, Payload = data }).Cast<TReceiveType>();
return this.ExecuteAsync(objData, cancellationToken);
}
catch (Exception ex)
{
var scoppedLogger = this.Context!.RequestServices.GetRequiredService<ILogger<WebSocketRouteBase<TReceiveType>>>().CreateScopedLogger(nameof(this.ExecuteAsync), string.Empty);
scoppedLogger.LogError(ex, "Failed to process data");
throw;
}
}
public abstract Task ExecuteAsync(TReceiveType? type, CancellationToken cancellationToken);
internal static WebSocketMessageConverterBase GetConverter(Type converterType, HttpContext context)
{
var constructors = converterType.GetConstructors();
foreach (var constructor in constructors)
{
if (constructor.GetCustomAttributes(false).Any(a => a is DoNotInjectAttribute))
{
continue;
}
var dependencies = constructor.GetParameters().Select(param => context.RequestServices.GetService(param.ParameterType));
if (dependencies.Any(d => d is null))
{
continue;
}
var route = constructor.Invoke(dependencies.ToArray());
return route.Cast<WebSocketMessageConverterBase>();
}
throw new InvalidOperationException($"Unable to resolve {converterType.Name}");
}
}
public abstract class WebSocketRouteBase<TReceiveType, TSendType> : WebSocketRouteBase<TReceiveType>
where TReceiveType : class, new()
{
private readonly Lazy<WebSocketMessageConverterBase> converter = new(() =>
{
var attribute = typeof(TSendType).GetCustomAttributes(true).First(a => a is WebSocketConverterAttributeBase).Cast<WebSocketConverterAttributeBase>();
var converter = Activator.CreateInstance(attribute.ConverterType)!.Cast<WebSocketMessageConverterBase>();
return converter;
});
public WebSocketRouteBase()
{
this.converter = new Lazy<WebSocketMessageConverterBase>(() =>
{
var attribute = typeof(TSendType).GetCustomAttributes(true).First(a => a is WebSocketConverterAttributeBase).Cast<WebSocketConverterAttributeBase>();
var parsedConverter = GetConverter(attribute.ConverterType, this.Context!);
return parsedConverter;
});
}
public Task SendMessage(TSendType sendType, CancellationToken cancellationToken)
{
try
{
var response = this.converter.Value.ConvertFromObject(sendType!);
return this.WebSocket!.SendAsync(response.Payload!, response.Type, response.EndOfMessage, cancellationToken);
}
catch (Exception ex)
{
var scoppedLogger = this.Context!.RequestServices.GetRequiredService<ILogger<WebSocketRouteBase<TReceiveType, TSendType>>>().CreateScopedLogger(nameof(this.SendMessage), string.Empty);
scoppedLogger.LogError(ex, "Failed to send data");
throw;
}
}
}