Skip to content

Commit fdbe935

Browse files
authored
feat(ISocketDataPropertyConverter): add ISocketDataPropertyConverter interface (#6422)
* feat(ISocketDataConverter): add ISocketDataConverter interface (#6420) * feat: 增加数据转换器设计 * refactor: 数据处理器增加数据转化器适配 * test: 增加单元测试 * refactor: 更新判断转换成功逻辑 * test: 更新单元测试 * feat: 增加 SocketDataConverter 标签 * feat: 增加 Parse 转化器 * test: 增加单元测试 * feat: 增加无符号数据类型转换 * test: 增加单元测试 * refactor: 精简代码 * test: 更新单元测试 * feat: 增加防止死锁逻辑 * test: 增加单元测试 * refactor: 重构代码 * feat: 增加转换器 * feat: 增加 GetConverterByType 扩展方法 * test: 更新单元测试 * test: 增加单元测试
1 parent 4fffebd commit fdbe935

31 files changed

+1147
-70
lines changed

src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
55

6+
using System.Reflection;
67
using System.Runtime.Versioning;
78
using System.Text;
89

@@ -76,20 +77,18 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPack
7677
}
7778

7879
/// <summary>
79-
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and a callback
80-
/// function for processing received data.
80+
/// Configures the specified <see cref="ITcpSocketClient"/> to use a data package adapter and a callback function
81+
/// for processing received data.
8182
/// </summary>
82-
/// <remarks>This method sets up the <paramref name="client"/> to use the provided <paramref
83-
/// name="adapter"/> for handling incoming data. The adapter processes the raw data received by the client and
84-
/// attempts to convert it into an instance of <typeparamref name="TEntity"/>. If the conversion is successful, the
85-
/// <paramref name="callback"/> is invoked with the converted entity; otherwise, it is invoked with <see
86-
/// langword="null"/>.</remarks>
87-
/// <typeparam name="TEntity">The type of the entity that the data package adapter will attempt to convert the received data into.</typeparam>
88-
/// <param name="client">The <see cref="ITcpSocketClient"/> instance to configure.</param>
89-
/// <param name="adapter">The <see cref="IDataPackageAdapter"/> instance responsible for handling and processing incoming data.</param>
90-
/// <param name="callback">A callback function to be invoked with the processed data of type <typeparamref name="TEntity"/>. The callback
91-
/// receives <see langword="null"/> if the data cannot be converted to <typeparamref name="TEntity"/>.</param>
92-
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
83+
/// <remarks>This method sets up the <paramref name="client"/> to process incoming data using the
84+
/// specified <paramref name="adapter"/> and <paramref name="socketDataConverter"/>. The <paramref
85+
/// name="callback"/> is called with the converted entity whenever data is received.</remarks>
86+
/// <typeparam name="TEntity">The type of the entity that the data will be converted to.</typeparam>
87+
/// <param name="client">The TCP socket client to configure.</param>
88+
/// <param name="adapter">The data package adapter responsible for handling incoming data.</param>
89+
/// <param name="socketDataConverter">The converter used to transform the received data into the specified entity type.</param>
90+
/// <param name="callback">The callback function to be invoked with the converted entity.</param>
91+
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, ISocketDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
9392
{
9493
// 设置 ITcpSocketClient 的回调函数
9594
client.ReceivedCallBack = async buffer =>
@@ -102,14 +101,57 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
102101
adapter.ReceivedCallBack = async buffer =>
103102
{
104103
TEntity? ret = default;
105-
if (adapter.TryConvertTo(buffer, out var t))
104+
if (socketDataConverter.TryConvertTo(buffer, out var t))
106105
{
107-
if (t is TEntity entity)
108-
{
109-
ret = entity;
110-
}
106+
ret = t;
111107
}
112108
await callback(ret);
113109
};
114110
}
111+
112+
/// <summary>
113+
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and callback
114+
/// function.
115+
/// </summary>
116+
/// <remarks>This method sets up the <paramref name="client"/> to use the specified <paramref
117+
/// name="adapter"/> for handling incoming data. If the <typeparamref name="TEntity"/> type is decorated with a <see
118+
/// cref="SocketDataConverterAttribute"/>, the associated converter is used to transform the data before invoking
119+
/// the <paramref name="callback"/>. The callback is called with the converted entity or <see langword="null"/> if
120+
/// conversion fails.</remarks>
121+
/// <typeparam name="TEntity">The type of entity that the data package adapter will handle.</typeparam>
122+
/// <param name="client">The TCP socket client to configure.</param>
123+
/// <param name="adapter">The data package adapter responsible for processing incoming data.</param>
124+
/// <param name="callback">The callback function to invoke with the processed entity of type <typeparamref name="TEntity"/>.</param>
125+
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
126+
{
127+
// 设置 ITcpSocketClient 的回调函数
128+
client.ReceivedCallBack = async buffer =>
129+
{
130+
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
131+
await adapter.HandlerAsync(buffer);
132+
};
133+
134+
var type = typeof(TEntity);
135+
var converterType = type.GetCustomAttribute<SocketDataConverterAttribute>();
136+
if (converterType is { Type: not null })
137+
{
138+
if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter<TEntity> socketDataConverter)
139+
{
140+
// 设置 DataPackageAdapter 的回调函数
141+
adapter.ReceivedCallBack = async buffer =>
142+
{
143+
TEntity? ret = default;
144+
if (socketDataConverter.TryConvertTo(buffer, out var t))
145+
{
146+
ret = t;
147+
}
148+
await callback(ret);
149+
};
150+
}
151+
}
152+
else
153+
{
154+
adapter.ReceivedCallBack = async buffer => await callback(default);
155+
}
156+
}
115157
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// Defines a method to convert raw socket data into a specified entity type.
10+
/// </summary>
11+
/// <typeparam name="TEntity">The type of entity to convert the data into.</typeparam>
12+
public interface ISocketDataConverter<TEntity>
13+
{
14+
/// <summary>
15+
/// Attempts to convert the specified data to an instance of <typeparamref name="TEntity"/>.
16+
/// </summary>
17+
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
18+
/// langword="false"/> and sets <paramref name="entity"/> to <see langword="null"/>.</remarks>
19+
/// <param name="data">The data to be converted, represented as a read-only memory block of bytes.</param>
20+
/// <param name="entity">When this method returns, contains the converted <typeparamref name="TEntity"/> if the conversion succeeded;
21+
/// otherwise, <see langword="null"/>.</param>
22+
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
23+
bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
///
10+
/// </summary>
11+
[AttributeUsage(AttributeTargets.Class)]
12+
public class SocketDataConverterAttribute : Attribute
13+
{
14+
/// <summary>
15+
/// Gets or sets the type of the <see cref="ISocketDataConverter{TEntity}"/>.
16+
/// </summary>
17+
public Type? Type { get; set; }
18+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
using System.Reflection;
7+
8+
namespace BootstrapBlazor.Components;
9+
10+
/// <summary>
11+
/// Provides a base class for converting socket data into a specified entity type.
12+
/// </summary>
13+
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
14+
public abstract class SocketDataConverterBase<TEntity> : ISocketDataConverter<TEntity>
15+
{
16+
/// <summary>
17+
/// <inheritdoc/>
18+
/// </summary>
19+
/// <param name="data"></param>
20+
/// <param name="entity"></param>
21+
/// <returns></returns>
22+
public abstract bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);
23+
24+
/// <summary>
25+
/// 将字节数据转换为指定实体类型的实例。
26+
/// </summary>
27+
/// <param name="data"></param>
28+
/// <param name="entity"></param>
29+
protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
30+
{
31+
// 使用 SocketDataFieldAttribute 特性获取数据转换规则
32+
var ret = false;
33+
if (entity != null)
34+
{
35+
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
36+
foreach (var p in properties)
37+
{
38+
var attr = p.GetCustomAttribute<SocketDataPropertyAttribute>(false);
39+
if (attr != null)
40+
{
41+
p.SetValue(entity, attr.ConvertTo(data));
42+
}
43+
}
44+
ret = true;
45+
}
46+
return ret;
47+
}
48+
}

src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,18 @@ public virtual async ValueTask HandlerAsync(ReadOnlyMemory<byte> data, Cancellat
4848
/// <inheritdoc/>
4949
/// </summary>
5050
/// <param name="data"></param>
51+
/// <param name="socketDataConverter"></param>
5152
/// <param name="entity"></param>
5253
/// <returns></returns>
53-
public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity)
54+
public virtual bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, ISocketDataConverter<TEntity> socketDataConverter, out TEntity? entity)
5455
{
55-
entity = null;
56-
return false;
56+
entity = default;
57+
var ret = socketDataConverter.TryConvertTo(data, out var v);
58+
if (ret)
59+
{
60+
entity = v;
61+
}
62+
return ret;
5763
}
5864

5965
/// <summary>

src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ public interface IDataPackageAdapter
4141
ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);
4242

4343
/// <summary>
44-
/// Attempts to convert the specified binary data into an object representation.
44+
/// Attempts to convert the specified byte data into an entity of type <typeparamref name="TEntity"/>.
4545
/// </summary>
46-
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
47-
/// langword="false"/> and sets <paramref name="entity"/> to <see langword="null"/>.</remarks>
48-
/// <param name="data">The binary data to be converted. Must not be empty.</param>
49-
/// <param name="entity">When this method returns <see langword="true"/>, contains the converted object. When this method returns <see
50-
/// langword="false"/>, contains <see langword="null"/>.</param>
46+
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
47+
/// langword="false"/> and sets <paramref name="entity"/> to its default value.</remarks>
48+
/// <typeparam name="TEntity">The type of the entity to convert the data to.</typeparam>
49+
/// <param name="data">The byte data to be converted.</param>
50+
/// <param name="socketDataConverter">The converter used to transform the byte data into an entity.</param>
51+
/// <param name="entity">When this method returns, contains the converted entity if the conversion was successful; otherwise, the default
52+
/// value for the type of the entity.</param>
5153
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
52-
bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity);
54+
bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, ISocketDataConverter<TEntity> socketDataConverter, out TEntity? entity);
5355
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
static class SocketDataPropertyExtensions
9+
{
10+
public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyAttribute attribute)
11+
{
12+
return attribute.GetConverterByType() ?? attribute.GetDefaultConverter();
13+
}
14+
15+
private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyAttribute attribute)
16+
{
17+
ISocketDataPropertyConverter? converter = null;
18+
var converterType = attribute.ConverterType;
19+
if (converterType != null)
20+
{
21+
var converterParameters = attribute.ConverterParameters;
22+
var c = Activator.CreateInstance(converterType, converterParameters);
23+
if(c is ISocketDataPropertyConverter v)
24+
{
25+
converter = v;
26+
}
27+
}
28+
return converter;
29+
}
30+
31+
private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyAttribute attribute)
32+
{
33+
ISocketDataPropertyConverter? converter = null;
34+
var type = attribute.Type;
35+
if (type != null)
36+
{
37+
if (type == typeof(byte[]))
38+
{
39+
converter = new SocketDataByteArrayConverter();
40+
}
41+
else if (type == typeof(string))
42+
{
43+
converter = new SocketDataStringConverter(attribute.EncodingName);
44+
}
45+
else if (type.IsEnum)
46+
{
47+
converter = new SocketDataEnumConverter(attribute.Type);
48+
}
49+
else if (type == typeof(bool))
50+
{
51+
converter = new SocketDataBoolConverter();
52+
}
53+
else if (type == typeof(short))
54+
{
55+
converter = new SocketDataInt16BigEndianConverter();
56+
}
57+
else if (type == typeof(int))
58+
{
59+
converter = new SocketDataInt32BigEndianConverter();
60+
}
61+
else if (type == typeof(long))
62+
{
63+
converter = new SocketDataInt64BigEndianConverter();
64+
}
65+
else if (type == typeof(float))
66+
{
67+
converter = new SocketDataSingleBigEndianConverter();
68+
}
69+
else if (type == typeof(double))
70+
{
71+
converter = new SocketDataDoubleBigEndianConverter();
72+
}
73+
else if (type == typeof(ushort))
74+
{
75+
converter = new SocketDataUInt16BigEndianConverter();
76+
}
77+
else if (type == typeof(uint))
78+
{
79+
converter = new SocketDataUInt32BigEndianConverter();
80+
}
81+
else if (type == typeof(ulong))
82+
{
83+
converter = new SocketDataUInt64BigEndianConverter();
84+
}
85+
}
86+
return converter;
87+
}
88+
89+
public static object? ConvertTo(this SocketDataPropertyAttribute attribute, ReadOnlyMemory<byte> data)
90+
{
91+
object? ret = null;
92+
var start = attribute.Offset;
93+
var length = attribute.Length;
94+
95+
if (data.Length >= start + length)
96+
{
97+
var buffer = data.Slice(start, length);
98+
var converter = attribute.GetConverter();
99+
if (converter != null)
100+
{
101+
ret = converter.Convert(buffer);
102+
}
103+
}
104+
return ret;
105+
}
106+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// Socket 数据转换器接口
10+
/// </summary>
11+
public interface ISocketDataPropertyConverter
12+
{
13+
/// <summary>
14+
/// 将数据转换为指定类型的对象
15+
/// </summary>
16+
/// <param name="data"></param>
17+
/// <returns></returns>
18+
object? Convert(ReadOnlyMemory<byte> data);
19+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Components;
7+
8+
/// <summary>
9+
/// Sokcet 数据转换为 bool 数据转换器
10+
/// </summary>
11+
public class SocketDataBoolConverter : ISocketDataPropertyConverter
12+
{
13+
/// <summary>
14+
/// <inheritdoc/>
15+
/// </summary>
16+
/// <param name="data"></param>
17+
public object? Convert(ReadOnlyMemory<byte> data)
18+
{
19+
var ret = false;
20+
if (data.Length == 1)
21+
{
22+
ret = data.Span[0] != 0x00;
23+
}
24+
return ret;
25+
}
26+
}

0 commit comments

Comments
 (0)