Skip to content

Commit a324fe4

Browse files
authored
feat(SocketDataConverterCollections): add global converter configuration function (#6449)
* feat: 增加 ISocketDataConverter 接口 * feat: 增加 SocketDataConverterCollections 配置类 * test: 增加单元测试 * test: 更新单元测试 * test: 增加单元测试 * doc: 更新文档 * feat: 增加 SocketDataConverterCollections 配置项自动获取属性标签逻辑
1 parent 4bc092d commit a324fe4

File tree

15 files changed

+427
-95
lines changed

15 files changed

+427
-95
lines changed

src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,22 @@ private async Task CreateClient()
8484
<p>数据适配器设计思路如下</p>
8585

8686
<ol class="ul-demo">
87-
<li>使用 <code>SocketDataConverterAttribute</code> 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code>ISocketDataConverter</code>
87+
<li>使用 <code>SocketDataTypeConverterAttribute</code> 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code>ISocketDataConverter</code>
8888
接口
8989
</li>
90-
<li>使用 <code>SocketDataPropertyAttribute</code> 标签约定如何转换数据类型 (Property) 属性值</li>
90+
<li>使用 <code>SocketDataPropertyConverterAttribute</code> 标签约定如何转换数据类型 (Property) 属性值</li>
9191
</ol>
9292

93-
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
93+
<Pre>[SocketDataTypeConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
9494
class MockEntity
9595
{
96-
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
96+
[SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)]
9797
public byte[]? Header { get; set; }
9898

99-
[SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)]
99+
[SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)]
100100
public byte[]? Body { get; set; }
101101

102-
[SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
102+
[SocketDataPropertyConverter(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
103103
public string? Value1 { get; set; }
104104
}</Pre>
105105

src/BootstrapBlazor.Server/Components/Samples/Sockets/DataEntities.razor

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,62 +7,30 @@
77
<DemoBlock Title="@Localizer["NormalTitle"]"
88
Introduction="@Localizer["NormalIntro"]"
99
Name="Normal" ShowCode="false">
10-
<p>通过 <code>SocketDataConverterAttribute</code> 类标签与 <code>SocketDataPropertyAttribute</code>
10+
<p>
11+
通过 <code>SocketDataTypeConverterAttribute</code> 类标签与 <code>SocketDataPropertyConverterAttribute</code>
1112
属性标签可以将通讯数据自动转化为我们系统中需要的业务实体类示例如下</p>
12-
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
13+
<Pre>[SocketDataTypeConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
1314
class MockEntity
1415
{
15-
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
16+
[SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)]
1617
public byte[]? Header { get; set; }
1718

18-
[SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)]
19+
[SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)]
1920
public byte[]? Body { get; set; }
2021

21-
[SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")]
22+
[SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")]
2223
public string? Value1 { get; set; }
2324

24-
[SocketDataProperty(Type = typeof(int), Offset = 8, Length = 1)]
25+
[SocketDataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)]
2526
public int Value2 { get; set; }
2627
}</Pre>
27-
<p class="code-label">1. <code>SocketDataConverter</code> 参数说明:</p>
28+
<p class="code-label">1. <code>SocketDataTypeConverter</code> 参数说明</p>
2829
<ul class="ul-demo">
2930
<li><b>Type</b>: 自定义转换器类型,组件库内置了 <code>SocketDataConverter</code> 泛型类建议看一下源码非常方便扩展出自己的转换器
3031
</li>
3132
</ul>
32-
<Pre>class MockEntitySocketDataConverter : SocketDataConverterBase&lt;MockEntity&gt;
33-
{
34-
public override bool TryConvertTo(ReadOnlyMemory&lt;byte&gt; data, [NotNullWhen(true)] out MockEntity? entity)
35-
{
36-
var v = new MockEntity();
37-
38-
// 调用基类的 Parse 方法即可
39-
var ret = Parse(data, v);
40-
entity = ret ? v : null;
41-
return ret;
42-
}
43-
}</Pre>
44-
<p><code>Parse</code> 方法也是可以重载自定义实现的,内置实现逻辑如下:</p>
45-
<Pre>protected virtual bool Parse(ReadOnlyMemory&lt;byte&gt; data, TEntity entity)
46-
{
47-
// 使用 SocketDataPropertyAttribute 特性获取数据转换规则
48-
var ret = false;
49-
if (entity != null)
50-
{
51-
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
52-
foreach (var p in properties)
53-
{
54-
var attr = p.GetCustomAttribute&lt;SocketDataPropertyAttribute&gt;(false);
55-
if (attr != null)
56-
{
57-
p.SetValue(entity, attr.ConvertTo(data));
58-
}
59-
}
60-
ret = true;
61-
}
62-
return ret;
63-
}</Pre>
64-
<p>即遍历所有有 <code>SocketDataPropertyAttribute</code> 标签的属性对其进行过自动赋值</p>
65-
<p class="code-label">2. <code>SocketDataPropertyAttribute</code> 参数说明</p>
33+
<p class="code-label">2. <code>SocketDataPropertyConverterAttribute</code> 参数说明</p>
6634
<ul class="ul-demo">
6735
<li><b>Type</b>: 转换目标数据类型</li>
6836
<li><b>Offset</b>: 数据偏移量即在接收到的数据中起始位置</li>
@@ -95,16 +63,16 @@ class MockEntity
9563
<li><code>SocketDataUInt64LittleEndianConverter</code> 转成 ulong 无符号整形小端读取</li>
9664
</ul>
9765
<p>自定义数据类型转化器示例</p>
98-
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
66+
<Pre>[SocketTypeDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
9967
class MockEntity
10068
{
101-
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
69+
[SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)]
10270
public byte[]? Header { get; set; }
10371

104-
[SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)]
72+
[SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)]
10573
public byte[]? Body { get; set; }
10674

107-
[SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
75+
[SocketDataPropertyConverter(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
10876
public string? Value1 { get; set; }
10977
}</Pre>
11078
<Pre>class FooConverter(string name) : ISocketDataPropertyConverter

src/BootstrapBlazor.Server/Locales/en-US.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7129,7 +7129,7 @@
71297129
"DataEntityTitle": "Socket Auto Entity",
71307130
"DataEntityDescription": "After receiving the communication data, it is automatically converted into the entity class required by the business",
71317131
"NormalTitle": "Basic usage",
7132-
"NormalIntro": "Enable automatic data conversion through the <code>SocketDataConverterAttribute</code> tag"
7132+
"NormalIntro": "Enable automatic data conversion through the <code>SocketDataTypeConverterAttribute</code> attribute"
71337133
},
71347134
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
71357135
"NetworkMonitorTitle": "NetworkMonitor",

src/BootstrapBlazor.Server/Locales/zh-CN.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7129,7 +7129,7 @@
71297129
"DataEntityTitle": "Socket 数据转化实体类",
71307130
"DataEntityDescription": "接收到通讯数据后自动转成业务需要的实体类",
71317131
"NormalTitle": "基本用法",
7132-
"NormalIntro": "通过 <code>SocketDataConverterAttribute</code> 标签开启数据自动转换功能"
7132+
"NormalIntro": "通过 <code>SocketDataTypeConverterAttribute</code> 标签开启数据自动转换功能"
71337133
},
71347134
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
71357135
"NetworkMonitorTitle": "NetworkMonitor 网络状态",

src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv
117117

118118
services.AddTabItemBindOptions();
119119
services.AddIconTheme();
120+
services.AddSocketDataConverters();
120121
return services;
121122
}
122123

@@ -212,7 +213,30 @@ static IServiceCollection AddTabItemBindOptions(this IServiceCollection services
212213
}
213214

214215
/// <summary>
215-
/// 增加第三方菜单路由与 Tab 捆绑字典配置
216+
/// 增加 Socket 数据转换器集合配置项服务
217+
/// </summary>
218+
/// <param name="services"></param>
219+
/// <returns></returns>
220+
static IServiceCollection AddSocketDataConverters(this IServiceCollection services)
221+
{
222+
services.AddOptionsMonitor<SocketDataConverterCollections>();
223+
return services;
224+
}
225+
226+
/// <summary>
227+
/// 配置第三方数据模型与 <see cref="SocketDataConverterCollections"/> 数据转换器集合配置扩展方法
228+
/// </summary>
229+
/// <param name="services"></param>
230+
/// <param name="configureOptions"></param>
231+
/// <returns></returns>
232+
public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action<SocketDataConverterCollections> configureOptions)
233+
{
234+
services.Configure(configureOptions);
235+
return services;
236+
}
237+
238+
/// <summary>
239+
/// 配置第三方菜单路由与 Tab 标签页捆绑字典扩展方法
216240
/// </summary>
217241
/// <param name="services"></param>
218242
/// <param name="configureOptions"></param>
@@ -236,7 +260,7 @@ static IServiceCollection AddIconTheme(this IServiceCollection services)
236260
}
237261

238262
/// <summary>
239-
/// IconThemeOptions 扩展配置方法
263+
/// 配置 <see cref="IconThemeOptions"/> 扩展方法
240264
/// </summary>
241265
/// <param name="services"></param>
242266
/// <param name="configureOptions"></param>

src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs

Lines changed: 46 additions & 12 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 Microsoft.Extensions.DependencyInjection;
67
using System.Reflection;
78
using System.Runtime.Versioning;
89
using System.Text;
@@ -115,7 +116,7 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
115116
/// </summary>
116117
/// <remarks>This method sets up the <paramref name="client"/> to use the specified <paramref
117118
/// 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+
/// cref="SocketDataTypeConverterAttribute"/>, the associated converter is used to transform the data before invoking
119120
/// the <paramref name="callback"/>. The callback is called with the converted entity or <see langword="null"/> if
120121
/// conversion fails.</remarks>
121122
/// <typeparam name="TEntity">The type of entity that the data package adapter will handle.</typeparam>
@@ -131,27 +132,60 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
131132
await adapter.HandlerAsync(buffer);
132133
};
133134

135+
ISocketDataConverter<TEntity>? converter = null;
136+
134137
var type = typeof(TEntity);
135-
var converterType = type.GetCustomAttribute<SocketDataConverterAttribute>();
138+
var converterType = type.GetCustomAttribute<SocketDataTypeConverterAttribute>();
136139
if (converterType is { Type: not null })
137140
{
141+
// 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器
138142
if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter<TEntity> socketDataConverter)
139143
{
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-
};
144+
converter = socketDataConverter;
150145
}
151146
}
152147
else
153148
{
149+
// 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器
150+
converter = client.GetSocketDataConverter<TEntity>();
151+
}
152+
153+
if (converter == null)
154+
{
155+
// 设置正常回调
154156
adapter.ReceivedCallBack = async buffer => await callback(default);
155157
}
158+
else
159+
{
160+
// 设置转化器
161+
adapter.SetDataAdapterCallback(converter, callback);
162+
}
163+
}
164+
165+
private static void SetDataAdapterCallback<TEntity>(this IDataPackageAdapter adapter, ISocketDataConverter<TEntity> converter, Func<TEntity?, Task> callback)
166+
{
167+
adapter.ReceivedCallBack = async buffer =>
168+
{
169+
TEntity? ret = default;
170+
if (converter.TryConvertTo(buffer, out var t))
171+
{
172+
ret = t;
173+
}
174+
await callback(ret);
175+
};
176+
}
177+
178+
private static ISocketDataConverter<TEntity>? GetSocketDataConverter<TEntity>(this ITcpSocketClient client)
179+
{
180+
ISocketDataConverter<TEntity>? converter = null;
181+
if (client is IServiceProvider provider)
182+
{
183+
var converters = provider.GetRequiredService<IOptions<SocketDataConverterCollections>>().Value;
184+
if (converters.TryGetTypeConverter<TEntity>(out var v))
185+
{
186+
converter = v;
187+
}
188+
}
189+
return converter;
156190
}
157191
}

src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@
55

66
namespace BootstrapBlazor.Components;
77

8+
/// <summary>
9+
/// Socket 数据转换器接口
10+
/// </summary>
11+
public interface ISocketDataConverter
12+
{
13+
14+
}
15+
816
/// <summary>
917
/// Defines a method to convert raw socket data into a specified entity type.
1018
/// </summary>
1119
/// <typeparam name="TEntity">The type of entity to convert the data into.</typeparam>
12-
public interface ISocketDataConverter<TEntity>
20+
public interface ISocketDataConverter<TEntity> : ISocketDataConverter
1321
{
1422
/// <summary>
1523
/// Attempts to convert the specified data to an instance of <typeparamref name="TEntity"/>.

src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverter.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,16 @@ namespace BootstrapBlazor.Components;
1111
/// Provides a base class for converting socket data into a specified entity type.
1212
/// </summary>
1313
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
14-
public class SocketDataConverter<TEntity> : ISocketDataConverter<TEntity>
14+
public class SocketDataConverter<TEntity>(SocketDataConverterCollections converters) : ISocketDataConverter<TEntity>
1515
{
16+
/// <summary>
17+
/// 构造函数
18+
/// </summary>
19+
public SocketDataConverter() : this(new())
20+
{
21+
22+
}
23+
1624
/// <summary>
1725
/// <inheritdoc/>
1826
/// </summary>
@@ -44,10 +52,14 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
4452
var ret = false;
4553
if (entity != null)
4654
{
55+
var unuseProperties = new List<PropertyInfo>(32);
56+
57+
// 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器
4758
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
4859
foreach (var p in properties)
4960
{
50-
var attr = p.GetCustomAttribute<SocketDataPropertyAttribute>(false);
61+
var attr = p.GetCustomAttribute<SocketDataPropertyConverterAttribute>(false)
62+
?? GetPropertyConverterAttribute(p);
5163
if (attr != null)
5264
{
5365
p.SetValue(entity, attr.ConvertTo(data));
@@ -57,4 +69,14 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
5769
}
5870
return ret;
5971
}
72+
73+
private SocketDataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo)
74+
{
75+
SocketDataPropertyConverterAttribute? attr = null;
76+
if (converters.TryGetPropertyConverter<TEntity>(propertyInfo, out var v))
77+
{
78+
attr = v;
79+
}
80+
return attr;
81+
}
6082
}

0 commit comments

Comments
 (0)