Skip to content

Commit 75e1cf7

Browse files
committed
2 parents f97e0ed + 2c0b1ff commit 75e1cf7

File tree

41 files changed

+1383
-84
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1383
-84
lines changed

README.zh-CN.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,27 @@
3333

3434
- Argo Zhang [链接地址](https://mvp.microsoft.com/en-us/PublicProfile/5004174)
3535
- Alex Chow [链接地址](https://mvp.microsoft.com/en-us/PublicProfile/5005078)
36-
- Guohao Wang [链接地址](https://mvp.microsoft.com/en-us/PublicProfile/5005089)
3736
- Xiang Ju [链接地址](https://mvp.microsoft.com/en-us/PublicProfile/5005108)
37+
- Guohao Wang [链接地址](https://mvp.microsoft.com/en-us/PublicProfile/5005089) (2023-2025)
3838

39-
## 生态伙伴
39+
## 生态伙伴 WTM
4040
WTM 快速开发框架,设计的核心理念就是 "尽一切可能提高开发效率"。WTM框架把常规编码结构化,重复编码自动化,它不仅是一个框架,它是强有力的生产力工具!目前 WTM 快速开发框架已深度集成 Blazor 欢迎大家使用 [传送门](https://wtmdoc.walkingtec.cn)
4141

4242
<a href="https://wtmdoc.walkingtec.cn" target="_blank"><img src="http://images.gitee.com/uploads/images/2021/0718/194451_5b6cff04_554725.png" width="100px" /></a>
4343

4444
WTM 快速开发框架,设计的核心理念就是 "尽一切可能提高开发效率"。WTM框架把常规编码结构化,重复编码自动化,它不仅是一个框架,它是强有力的生产力工具!目前 WTM 快速开发框架已深度集成 Blazor 欢迎大家使用 [传送门](https://wtmdoc.walkingtec.cn)
4545

46+
## 生态伙伴 ThingsGateway
47+
ThingsGateway 边缘网关,致力于打造“高性能、插件扩展、易集成”的工业物联网网关解决方案。
48+
49+
<a href="https://thingsgateway.cn" target="_blank"><img src="https://foruda.gitee.com/images/1752651511062082157/c6a923f8_554725.png" width="100px" /></a>
50+
51+
ThingsGateway 将设备接入、数据上传等功能模块化,降低工业协议开发门槛,自动处理线程调度与数据缓存,极大提升开发效率与系统稳定性。
52+
它不仅是一个网关框架,更是构建数字化工厂、边缘智能系统的核心工具!
53+
ThingsGateway 已集成 Bootstrap Blazor 实现配置管理界面,轻松构建跨平台的边缘网关管理系统 [传送门](https://thingsgateway.cn)
54+
55+
56+
4657
## 开发环境搭建
4758
1. 安装 .net core sdk 最新版 [官方网址](http://www.microsoft.com/net/download)
4859
2. 安装 Visual Studio 2022 最新版 [官方网址](https://visualstudio.microsoft.com/vs/getting-started/)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
@page "/socket/data-entity"
2+
@inject IStringLocalizer<DataEntities> Localizer
3+
4+
<h3>@Localizer["DataEntityTitle"]</h3>
5+
<h4>@Localizer["DataEntityDescription"]</h4>
6+
7+
<DemoBlock Title="@Localizer["NormalTitle"]"
8+
Introduction="@Localizer["NormalIntro"]"
9+
Name="Normal" ShowCode="false">
10+
<p>通过 <code>SocketDataConverterAttribute</code> 类标签与 <code>SocketDataPropertyAttribute</code>
11+
属性标签可以将通讯数据自动转化为我们系统中需要的业务实体类,示例如下:</p>
12+
<Pre>[SocketDataConverter(Type = typeof(MockEntitySocketDataConverter))]
13+
class MockEntity
14+
{
15+
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
16+
public byte[]? Header { get; set; }
17+
18+
[SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)]
19+
public byte[]? Body { get; set; }
20+
21+
[SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")]
22+
public string? Value1 { get; set; }
23+
24+
[SocketDataProperty(Type = typeof(int), Offset = 8, Length = 1)]
25+
public int Value2 { get; set; }
26+
}</Pre>
27+
<p class="code-label">1. <code>SocketDataConverter</code> 参数说明:</p>
28+
<ul class="ul-demo">
29+
<li><b>Type</b>: 自定义转换器类型,组件库内置了 <code>SocketDataConverterBase</code> 泛型基类,继承后非常方便扩展出自己的转换器,重载
30+
<code>TryConvertTo</code> 方法调用基类的 <code>Parse</code> 基本就完成了 99% 的工作量,也可以完全自己解析通讯电文到实体类的各个属性上
31+
</li>
32+
</ul>
33+
<Pre>class MockEntitySocketDataConverter : SocketDataConverterBase&lt;MockEntity&gt;
34+
{
35+
public override bool TryConvertTo(ReadOnlyMemory&lt;byte&gt; data, [NotNullWhen(true)] out MockEntity? entity)
36+
{
37+
var v = new MockEntity();
38+
39+
// 调用基类的 Parse 方法即可
40+
var ret = Parse(data, v);
41+
entity = ret ? v : null;
42+
return ret;
43+
}
44+
}</Pre>
45+
<p><code>Parse</code> 方法也是可以重载自定义实现的,内置实现逻辑如下:</p>
46+
<Pre>protected virtual bool Parse(ReadOnlyMemory&lt;byte&gt; data, TEntity entity)
47+
{
48+
// 使用 SocketDataPropertyAttribute 特性获取数据转换规则
49+
var ret = false;
50+
if (entity != null)
51+
{
52+
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
53+
foreach (var p in properties)
54+
{
55+
var attr = p.GetCustomAttribute&lt;SocketDataPropertyAttribute&gt;(false);
56+
if (attr != null)
57+
{
58+
p.SetValue(entity, attr.ConvertTo(data));
59+
}
60+
}
61+
ret = true;
62+
}
63+
return ret;
64+
}</Pre>
65+
<p>即遍历所有有 <code>SocketDataPropertyAttribute</code> 标签的属性对其进行过自动赋值</p>
66+
<p class="code-label">2. <code>SocketDataPropertyAttribute</code> 参数说明</p>
67+
<ul class="ul-demo">
68+
<li><b>Type</b>: 转换目标数据类型</li>
69+
<li><b>Offset</b>: 数据偏移量,即在接收到的数据中起始位置</li>
70+
<li><b>Length</b>: 数据长度,即在接收到的数据中占的长度</li>
71+
<li><b>EncodingName</b>: 转成字符串时需要的编码名称,默认 null 使用 <code>utf-8</code> 编码</li>
72+
<li><b>ConverterType</b>: 自定义转化器类型,继承 <code>ISocketDataPropertyConverter</code> 接口</li>
73+
<li><b>ConverterParameters</b>: 自定义转化器类型构造函数所需的参数,默认 null</li>
74+
</ul>
75+
<p>组件库内置了大量数据类型转换器</p>
76+
<ul class="ul-demo">
77+
<li><code>SocketDataByteArrayConverter</code> 转成 byte[] 数组类型</li>
78+
<li><code>SocketDataStringConverter</code> 转成 string 字符串类型</li>
79+
<li><code>SocketDataEnumConverter</code> 转成 enum 枚举类型</li>
80+
<li><code>SocketDataBoolConverter</code> 转成 bool 布尔类型</li>
81+
<li><code>SocketDataInt16BigEndianConverter</code> 转成 short 整形大端读取</li>
82+
<li><code>SocketDataInt32BigEndianConverter</code> 转成 int 整形大端读取</li>
83+
<li><code>SocketDataInt64BigEndianConverter</code> 转成 long 长整形大端读取</li>
84+
<li><code>SocketDataSingleBigEndianConverter</code> 转成 float 单精度浮点数大端读取</li>
85+
<li><code>SocketDataDoubleBigEndianConverter</code> 转成 double 双精度浮点数大端读取</li>
86+
<li><code>SocketDataUInt16BigEndianConverter</code> 转成 ushort 无符号整形大端读取</li>
87+
<li><code>SocketDataUInt32BigEndianConverter</code> 转成 uint 无符号整形大端读取</li>
88+
<li><code>SocketDataUInt64BigEndianConverter</code> 转成 ulong 无符号长整形大端读取</li>
89+
<li><code>SocketDataInt16LittleEndianConverter</code> 转成 short 整形小端读取</li>
90+
<li><code>SocketDataInt32LittleEndianConverter</code> 转成 int 整形小端读取</li>
91+
<li><code>SocketDataInt64LittleEndianConverter</code> 转成 long 长整形小端读取</li>
92+
<li><code>SocketDataSingleLittleEndianConverter</code> 转成 float 单精度浮点数小端读取</li>
93+
<li><code>SocketDataDoubleLittleEndianConverter</code> 转成 double 双精度浮点数小端读取</li>
94+
<li><code>SocketDataUInt16LittleEndianConverter</code> 转成 ushort 无符号整形小端读取</li>
95+
<li><code>SocketDataUInt32LittleEndianConverter</code> 转成 uint 无符号整形小端读取</li>
96+
<li><code>SocketDataUInt64LittleEndianConverter</code> 转成 ulong 无符号整形小端读取</li>
97+
</ul>
98+
<p>自定义数据类型转化器示例</p>
99+
<Pre>[SocketDataConverter(Type = typeof(MockEntitySocketDataConverter))]
100+
class MockEntity
101+
{
102+
[SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)]
103+
public byte[]? Header { get; set; }
104+
105+
[SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)]
106+
public byte[]? Body { get; set; }
107+
108+
[SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])]
109+
public string? Value1 { get; set; }
110+
}
111+
112+
class FooConverter(string name) : ISocketDataPropertyConverter
113+
{
114+
public object? Convert(ReadOnlyMemory&lt;byte&gt; data)
115+
{
116+
return new Foo() { Id = data.Span[0], Name = name };
117+
}
118+
}</Pre>
119+
<p>更多技术细节可以参考以上内置提供的转换器源码</p>
120+
</DemoBlock>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.Server.Components.Samples.Sockets;
7+
8+
/// <summary>
9+
/// 数据转实体类示例
10+
/// </summary>
11+
public partial class DataEntities
12+
{
13+
14+
}

src/BootstrapBlazor.Server/Extensions/MenusLocalizerExtensions.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,27 +209,29 @@ void AddSocket(DemoMenuItem item)
209209
{
210210
new()
211211
{
212-
IsNew = true,
213212
Text = Localizer["SocketManualReceive"],
214213
Url = "socket/manual-receive"
215214
},
216215
new()
217216
{
218-
IsNew = true,
219217
Text = Localizer["SocketAutoReceive"],
220218
Url = "socket/auto-receive"
221219
},
222220
new()
223221
{
224-
IsNew = true,
225222
Text = Localizer["DataPackageAdapter"],
226223
Url = "socket/adapter"
227224
},
228225
new()
229226
{
230-
IsNew = true,
231227
Text = Localizer["SocketAutoConnect"],
232228
Url = "socket/auto-connect"
229+
},
230+
new()
231+
{
232+
IsNew = true,
233+
Text = Localizer["SocketDataEntity"],
234+
Url = "socket/data-entity"
233235
}
234236
};
235237
AddBadge(item, count: 1);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4841,6 +4841,7 @@
48414841
"SocketManualReceive": "Manual Receive",
48424842
"DataPackageAdapter": "DataPackageAdapter",
48434843
"SocketAutoConnect": "Reconnect",
4844+
"SocketDataEntity": "DataEntity",
48444845
"NetworkMonitor": "Network Monitor"
48454846
},
48464847
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
@@ -7121,6 +7122,12 @@
71217122
"NormalTitle": "Basic usage",
71227123
"NormalIntro": "Enable automatic reconnection by setting <code>IsAutoReconnect</code>"
71237124
},
7125+
"BootstrapBlazor.Server.Components.Samples.Sockets.DataEntities": {
7126+
"DataEntityTitle": "Socket Auto Entity",
7127+
"DataEntityDescription": "After receiving the communication data, it is automatically converted into the entity class required by the business",
7128+
"NormalTitle": "Basic usage",
7129+
"NormalIntro": "Enable automatic data conversion through the <code>SocketDataConverterAttribute</code> tag"
7130+
},
71247131
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
71257132
"NetworkMonitorTitle": "NetworkMonitor",
71267133
"NetworkMonitorDescription": "Use the browser native API <code>navigator.connection</code> to display the current network status in real time",

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4841,6 +4841,7 @@
48414841
"SocketManualReceive": "手动接收数据",
48424842
"DataPackageAdapter": "数据处理器",
48434843
"SocketAutoConnect": "自动重连",
4844+
"SocketDataEntity": "通讯数据转实体类",
48444845
"NetworkMonitor": "网络状态 NetworkMonitor"
48454846
},
48464847
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
@@ -7121,6 +7122,12 @@
71217122
"NormalTitle": "基本用法",
71227123
"NormalIntro": "通过设置 <code>IsAutoReconnect</code> 开启自动重连机制"
71237124
},
7125+
"BootstrapBlazor.Server.Components.Samples.Sockets.DataEntities": {
7126+
"DataEntityTitle": "Socket 数据转化实体类",
7127+
"DataEntityDescription": "接收到通讯数据后自动转成业务需要的实体类",
7128+
"NormalTitle": "基本用法",
7129+
"NormalIntro": "通过 <code>SocketDataConverterAttribute</code> 标签开启数据自动转换功能"
7130+
},
71247131
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
71257132
"NetworkMonitorTitle": "NetworkMonitor 网络状态",
71267133
"NetworkMonitorDescription": "使用浏览器原生 api <code>navigator.connection</code> 实时显示当前网络状态",

src/BootstrapBlazor.Server/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@
247247
"socket/auto-receive": "Sockets\\AutoReceives",
248248
"socket/adapter": "Sockets\\Adapters",
249249
"socket/auto-connect": "Sockets\\AutoReconnects",
250+
"socket/data-entity": "Sockets\\DataEntities",
250251
"network-monitor": "NetworkMonitors"
251252
},
252253
"video": {

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.8.1</Version>
4+
<Version>9.8.2-beta01</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ protected override void OnParametersSet()
303303
}
304304
else
305305
{
306-
SelectedValue = Value == null ? DateTime.MinValue : (DateTime)(object)Value;
306+
SelectedValue = Value == null ? (MinValue ?? DateTime.MinValue) : (DateTime)(object)Value;
307307
}
308308

309309
if (MinValue > SelectedValue)

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
}

0 commit comments

Comments
 (0)