Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 60 additions & 18 deletions src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using System.Reflection;
using System.Runtime.Versioning;
using System.Text;

Expand Down Expand Up @@ -76,20 +77,18 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPack
}

/// <summary>
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and a callback
/// function for processing received data.
/// Configures the specified <see cref="ITcpSocketClient"/> to use a data package adapter and a callback function
/// for processing received data.
/// </summary>
/// <remarks>This method sets up the <paramref name="client"/> to use the provided <paramref
/// name="adapter"/> for handling incoming data. The adapter processes the raw data received by the client and
/// attempts to convert it into an instance of <typeparamref name="TEntity"/>. If the conversion is successful, the
/// <paramref name="callback"/> is invoked with the converted entity; otherwise, it is invoked with <see
/// langword="null"/>.</remarks>
/// <typeparam name="TEntity">The type of the entity that the data package adapter will attempt to convert the received data into.</typeparam>
/// <param name="client">The <see cref="ITcpSocketClient"/> instance to configure.</param>
/// <param name="adapter">The <see cref="IDataPackageAdapter"/> instance responsible for handling and processing incoming data.</param>
/// <param name="callback">A callback function to be invoked with the processed data of type <typeparamref name="TEntity"/>. The callback
/// receives <see langword="null"/> if the data cannot be converted to <typeparamref name="TEntity"/>.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
/// <remarks>This method sets up the <paramref name="client"/> to process incoming data using the
/// specified <paramref name="adapter"/> and <paramref name="socketDataConverter"/>. The <paramref
/// name="callback"/> is called with the converted entity whenever data is received.</remarks>
/// <typeparam name="TEntity">The type of the entity that the data will be converted to.</typeparam>
/// <param name="client">The TCP socket client to configure.</param>
/// <param name="adapter">The data package adapter responsible for handling incoming data.</param>
/// <param name="socketDataConverter">The converter used to transform the received data into the specified entity type.</param>
/// <param name="callback">The callback function to be invoked with the converted entity.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, ISocketDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
{
// 设置 ITcpSocketClient 的回调函数
client.ReceivedCallBack = async buffer =>
Expand All @@ -102,14 +101,57 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
adapter.ReceivedCallBack = async buffer =>
{
TEntity? ret = default;
if (adapter.TryConvertTo(buffer, out var t))
if (socketDataConverter.TryConvertTo(buffer, out var t))
{
if (t is TEntity entity)
{
ret = entity;
}
ret = t;
}
await callback(ret);
};
}

/// <summary>
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and callback
/// function.
/// </summary>
/// <remarks>This method sets up the <paramref name="client"/> to use the specified <paramref
/// name="adapter"/> for handling incoming data. If the <typeparamref name="TEntity"/> type is decorated with a <see
/// cref="SocketDataConverterAttribute"/>, the associated converter is used to transform the data before invoking
/// the <paramref name="callback"/>. The callback is called with the converted entity or <see langword="null"/> if
/// conversion fails.</remarks>
/// <typeparam name="TEntity">The type of entity that the data package adapter will handle.</typeparam>
/// <param name="client">The TCP socket client to configure.</param>
/// <param name="adapter">The data package adapter responsible for processing incoming data.</param>
/// <param name="callback">The callback function to invoke with the processed entity of type <typeparamref name="TEntity"/>.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
{
// 设置 ITcpSocketClient 的回调函数
client.ReceivedCallBack = async buffer =>
{
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
await adapter.HandlerAsync(buffer);
};

var type = typeof(TEntity);
var converterType = type.GetCustomAttribute<SocketDataConverterAttribute>();
if (converterType is { Type: not null })
{
if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter<TEntity> socketDataConverter)
{
// 设置 DataPackageAdapter 的回调函数
adapter.ReceivedCallBack = async buffer =>
{
TEntity? ret = default;
if (socketDataConverter.TryConvertTo(buffer, out var t))
{
ret = t;
}
await callback(ret);
};
}
}
else
{
adapter.ReceivedCallBack = async buffer => await callback(default);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Defines a method to convert raw socket data into a specified entity type.
/// </summary>
/// <typeparam name="TEntity">The type of entity to convert the data into.</typeparam>
public interface ISocketDataConverter<TEntity>
{
/// <summary>
/// Attempts to convert the specified data to an instance of <typeparamref name="TEntity"/>.
/// </summary>
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
/// langword="false"/> and sets <paramref name="entity"/> to <see langword="null"/>.</remarks>
/// <param name="data">The data to be converted, represented as a read-only memory block of bytes.</param>
/// <param name="entity">When this method returns, contains the converted <typeparamref name="TEntity"/> if the conversion succeeded;
/// otherwise, <see langword="null"/>.</param>
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
///
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SocketDataConverterAttribute : Attribute
{
/// <summary>
/// Gets or sets the type of the <see cref="ISocketDataConverter{TEntity}"/>.
/// </summary>
public Type? Type { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using System.Reflection;

namespace BootstrapBlazor.Components;

/// <summary>
/// Provides a base class for converting socket data into a specified entity type.
/// </summary>
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
public abstract class SocketDataConverterBase<TEntity> : ISocketDataConverter<TEntity>
{
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="data"></param>
/// <param name="entity"></param>
/// <returns></returns>
public abstract bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);

/// <summary>
/// 将字节数据转换为指定实体类型的实例。
/// </summary>
/// <param name="data"></param>
/// <param name="entity"></param>
protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
{
// 使用 SocketDataFieldAttribute 特性获取数据转换规则
var ret = false;
if (entity != null)
{
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
foreach (var p in properties)
{
var attr = p.GetCustomAttribute<SocketDataPropertyAttribute>(false);
if (attr != null)
{
p.SetValue(entity, attr.ConvertTo(data));
}
}
ret = true;
}
return ret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,18 @@ public virtual async ValueTask HandlerAsync(ReadOnlyMemory<byte> data, Cancellat
/// <inheritdoc/>
/// </summary>
/// <param name="data"></param>
/// <param name="socketDataConverter"></param>
/// <param name="entity"></param>
/// <returns></returns>
public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity)
public virtual bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, ISocketDataConverter<TEntity> socketDataConverter, out TEntity? entity)
{
entity = null;
return false;
entity = default;
var ret = socketDataConverter.TryConvertTo(data, out var v);
if (ret)
{
entity = v;
}
return ret;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ public interface IDataPackageAdapter
ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);

/// <summary>
/// Attempts to convert the specified binary data into an object representation.
/// Attempts to convert the specified byte data into an entity of type <typeparamref name="TEntity"/>.
/// </summary>
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
/// langword="false"/> and sets <paramref name="entity"/> to <see langword="null"/>.</remarks>
/// <param name="data">The binary data to be converted. Must not be empty.</param>
/// <param name="entity">When this method returns <see langword="true"/>, contains the converted object. When this method returns <see
/// langword="false"/>, contains <see langword="null"/>.</param>
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
/// langword="false"/> and sets <paramref name="entity"/> to its default value.</remarks>
/// <typeparam name="TEntity">The type of the entity to convert the data to.</typeparam>
/// <param name="data">The byte data to be converted.</param>
/// <param name="socketDataConverter">The converter used to transform the byte data into an entity.</param>
/// <param name="entity">When this method returns, contains the converted entity if the conversion was successful; otherwise, the default
/// value for the type of the entity.</param>
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity);
bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, ISocketDataConverter<TEntity> socketDataConverter, out TEntity? entity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

static class SocketDataPropertyExtensions
{
public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyAttribute attribute)
{
return attribute.GetConverterByType() ?? attribute.GetDefaultConverter();
}

private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyAttribute attribute)
{
ISocketDataPropertyConverter? converter = null;
var converterType = attribute.ConverterType;
if (converterType != null)
{
var converterParameters = attribute.ConverterParameters;
var c = Activator.CreateInstance(converterType, converterParameters);
if(c is ISocketDataPropertyConverter v)
{
converter = v;
}
}
return converter;
}

private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyAttribute attribute)
{
ISocketDataPropertyConverter? converter = null;
var type = attribute.Type;
if (type != null)
{
if (type == typeof(byte[]))
{
converter = new SocketDataByteArrayConverter();
}
else if (type == typeof(string))
{
converter = new SocketDataStringConverter(attribute.EncodingName);
}
else if (type.IsEnum)
{
converter = new SocketDataEnumConverter(attribute.Type);
}
else if (type == typeof(bool))
{
converter = new SocketDataBoolConverter();
}
else if (type == typeof(short))
{
converter = new SocketDataInt16BigEndianConverter();
}
else if (type == typeof(int))
{
converter = new SocketDataInt32BigEndianConverter();
}
else if (type == typeof(long))
{
converter = new SocketDataInt64BigEndianConverter();
}
else if (type == typeof(float))
{
converter = new SocketDataSingleBigEndianConverter();
}
else if (type == typeof(double))
{
converter = new SocketDataDoubleBigEndianConverter();
}
else if (type == typeof(ushort))
{
converter = new SocketDataUInt16BigEndianConverter();
}
else if (type == typeof(uint))
{
converter = new SocketDataUInt32BigEndianConverter();
}
else if (type == typeof(ulong))
{
converter = new SocketDataUInt64BigEndianConverter();
}
}
return converter;
}

public static object? ConvertTo(this SocketDataPropertyAttribute attribute, ReadOnlyMemory<byte> data)
{
object? ret = null;
var start = attribute.Offset;
var length = attribute.Length;

if (data.Length >= start + length)
{
var buffer = data.Slice(start, length);
var converter = attribute.GetConverter();
if (converter != null)
{
ret = converter.Convert(buffer);
}
}
return ret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Socket 数据转换器接口
/// </summary>
public interface ISocketDataPropertyConverter
{
/// <summary>
/// 将数据转换为指定类型的对象
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
object? Convert(ReadOnlyMemory<byte> data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Sokcet 数据转换为 bool 数据转换器
/// </summary>
public class SocketDataBoolConverter : ISocketDataPropertyConverter
{
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="data"></param>
public object? Convert(ReadOnlyMemory<byte> data)
{
var ret = false;
if (data.Length == 1)
{
ret = data.Span[0] != 0x00;
}
return ret;
}
}
Loading
Loading