mirror of
https://github.com/AlexMacocian/Net.Sdk.Web.Extensions.git
synced 2026-07-24 12:06:27 +00:00
Add project files.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user