Skip to content

Commit fed7837

Browse files
authored
feat(TcpSocket): add TcpSocket project (#498)
* feat: 增加 Socket 相关服务 * test: 更新单元测试 * refactor: 更改类命名空间 * chore: 增加命名空间 * chore: 更改 Utility 扩展类
1 parent 7071e2f commit fed7837

66 files changed

Lines changed: 4362 additions & 8 deletions

File tree

Some content is hidden

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

BootstrapBlazor.Extensions.sln

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Mermaid", "
156156
EndProject
157157
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.MeiliSearch", "src\components\BootstrapBlazor.MeiliSearch\BootstrapBlazor.MeiliSearch.csproj", "{4B086A62-5F5A-47BC-921F-35803F26DD68}"
158158
EndProject
159-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniTestSvgIcon", "test\UniTestSvgIcon\UniTestSvgIcon.csproj", "{F965576B-A801-4473-85FE-E100125FDEF5}"
160-
EndProject
161159
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.RDKit", "src\components\BootstrapBlazor.RDKit\BootstrapBlazor.RDKit.csproj", "{7328E464-AE3C-4277-BEC3-422C56637066}"
162160
EndProject
163161
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.SmilesDrawer", "src\components\BootstrapBlazor.SmilesDrawer\BootstrapBlazor.SmilesDrawer.csproj", "{84823875-1B07-4CCE-A009-29AEF90C6C10}"
@@ -190,6 +188,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Vditor", "s
190188
EndProject
191189
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OfficeViewer", "src\components\BootstrapBlazor.OfficeViewer\BootstrapBlazor.OfficeViewer.csproj", "{2436940C-5920-D801-8A81-721F4C20A355}"
192190
EndProject
191+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Socket", "src\extensions\BootstrapBlazor.Socket\BootstrapBlazor.Socket.csproj", "{965F1512-57DC-4621-9C74-E059A14BB866}"
192+
EndProject
193+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.TcpSocket", "src\extensions\BootstrapBlazor.TcpSocket\BootstrapBlazor.TcpSocket.csproj", "{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}"
194+
EndProject
195+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestTcpSocket", "test\UnitTestTcpSocket\UnitTestTcpSocket.csproj", "{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}"
196+
EndProject
197+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestSvgIcon", "test\UnitTestSvgIcon\UnitTestSvgIcon.csproj", "{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}"
198+
EndProject
193199
Global
194200
GlobalSection(SolutionConfigurationPlatforms) = preSolution
195201
Debug|Any CPU = Debug|Any CPU
@@ -448,10 +454,6 @@ Global
448454
{4B086A62-5F5A-47BC-921F-35803F26DD68}.Debug|Any CPU.Build.0 = Debug|Any CPU
449455
{4B086A62-5F5A-47BC-921F-35803F26DD68}.Release|Any CPU.ActiveCfg = Release|Any CPU
450456
{4B086A62-5F5A-47BC-921F-35803F26DD68}.Release|Any CPU.Build.0 = Release|Any CPU
451-
{F965576B-A801-4473-85FE-E100125FDEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
452-
{F965576B-A801-4473-85FE-E100125FDEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
453-
{F965576B-A801-4473-85FE-E100125FDEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
454-
{F965576B-A801-4473-85FE-E100125FDEF5}.Release|Any CPU.Build.0 = Release|Any CPU
455457
{7328E464-AE3C-4277-BEC3-422C56637066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
456458
{7328E464-AE3C-4277-BEC3-422C56637066}.Debug|Any CPU.Build.0 = Debug|Any CPU
457459
{7328E464-AE3C-4277-BEC3-422C56637066}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -516,6 +518,22 @@ Global
516518
{2436940C-5920-D801-8A81-721F4C20A355}.Debug|Any CPU.Build.0 = Debug|Any CPU
517519
{2436940C-5920-D801-8A81-721F4C20A355}.Release|Any CPU.ActiveCfg = Release|Any CPU
518520
{2436940C-5920-D801-8A81-721F4C20A355}.Release|Any CPU.Build.0 = Release|Any CPU
521+
{965F1512-57DC-4621-9C74-E059A14BB866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
522+
{965F1512-57DC-4621-9C74-E059A14BB866}.Debug|Any CPU.Build.0 = Debug|Any CPU
523+
{965F1512-57DC-4621-9C74-E059A14BB866}.Release|Any CPU.ActiveCfg = Release|Any CPU
524+
{965F1512-57DC-4621-9C74-E059A14BB866}.Release|Any CPU.Build.0 = Release|Any CPU
525+
{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
526+
{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
527+
{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
528+
{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Release|Any CPU.Build.0 = Release|Any CPU
529+
{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
530+
{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
531+
{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
532+
{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Release|Any CPU.Build.0 = Release|Any CPU
533+
{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
534+
{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
535+
{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
536+
{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Release|Any CPU.Build.0 = Release|Any CPU
519537
EndGlobalSection
520538
GlobalSection(SolutionProperties) = preSolution
521539
HideSolutionNode = FALSE
@@ -588,7 +606,6 @@ Global
588606
{29E47F4A-CE03-42B5-BDAA-FB4B40D4C897} = {FF1089BE-C704-4374-B629-C57C08E1798F}
589607
{DA2198E1-2CA9-EE53-926B-7950AB4B5EBF} = {FF1089BE-C704-4374-B629-C57C08E1798F}
590608
{4B086A62-5F5A-47BC-921F-35803F26DD68} = {FF1089BE-C704-4374-B629-C57C08E1798F}
591-
{F965576B-A801-4473-85FE-E100125FDEF5} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE}
592609
{7328E464-AE3C-4277-BEC3-422C56637066} = {FF1089BE-C704-4374-B629-C57C08E1798F}
593610
{84823875-1B07-4CCE-A009-29AEF90C6C10} = {FF1089BE-C704-4374-B629-C57C08E1798F}
594611
{AA4EDA37-1D81-4235-A7F6-F1C112B364EF} = {FF1089BE-C704-4374-B629-C57C08E1798F}
@@ -605,6 +622,10 @@ Global
605622
{4757B038-70E4-40B0-9B73-700EE5632B07} = {FF1089BE-C704-4374-B629-C57C08E1798F}
606623
{D417E1B9-D146-4983-81D0-79F3193B322B} = {FF1089BE-C704-4374-B629-C57C08E1798F}
607624
{2436940C-5920-D801-8A81-721F4C20A355} = {FF1089BE-C704-4374-B629-C57C08E1798F}
625+
{965F1512-57DC-4621-9C74-E059A14BB866} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2}
626+
{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2}
627+
{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE}
628+
{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE}
608629
EndGlobalSection
609630
GlobalSection(ExtensibilityGlobals) = postSolution
610631
SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Version>9.0.0-beta01</Version>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<PackageTags>BootstrapBlazor Socket</PackageTags>
9+
<Description>BootstrapBlazor extensions of Socket</Description>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Using Include="BootstrapBlazor.DataAdapters" />
14+
<Using Include="BootstrapBlazor.DataHandlers" />
15+
<Using Include="BootstrapBlazor.DataConverters" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
namespace BootstrapBlazor.DataAdapters;
6+
7+
/// <summary>
8+
/// Provides a base implementation for adapting data packages between different systems or formats.
9+
/// </summary>
10+
/// <remarks>This abstract class serves as a foundation for implementing custom data package adapters. It defines
11+
/// common methods for sending, receiving, and handling data packages, as well as a property for accessing the
12+
/// associated data package handler. Derived classes should override the virtual methods to provide specific behavior
13+
/// for handling data packages.</remarks>
14+
public class DataPackageAdapter : IDataPackageAdapter
15+
{
16+
/// <summary>
17+
/// <inheritdoc/>
18+
/// </summary>
19+
public Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
20+
21+
/// <summary>
22+
/// <inheritdoc/>
23+
/// </summary>
24+
public IDataPackageHandler? DataPackageHandler { get; set; }
25+
26+
/// <summary>
27+
/// <inheritdoc/>
28+
/// </summary>
29+
/// <param name="data"></param>
30+
/// <param name="token"></param>
31+
/// <returns></returns>
32+
public virtual async ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token = default)
33+
{
34+
if (DataPackageHandler != null)
35+
{
36+
if (DataPackageHandler.ReceivedCallBack == null)
37+
{
38+
DataPackageHandler.ReceivedCallBack = OnHandlerReceivedCallBack;
39+
}
40+
41+
// 如果存在数据处理器则调用其处理方法
42+
await DataPackageHandler.HandlerAsync(data, token);
43+
}
44+
}
45+
46+
/// <summary>
47+
/// <inheritdoc/>
48+
/// </summary>
49+
/// <param name="data"></param>
50+
/// <param name="socketDataConverter"></param>
51+
/// <param name="entity"></param>
52+
/// <returns></returns>
53+
public virtual bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, IDataConverter<TEntity> socketDataConverter, out TEntity? entity)
54+
{
55+
entity = default;
56+
var ret = socketDataConverter.TryConvertTo(data, out var v);
57+
if (ret)
58+
{
59+
entity = v;
60+
}
61+
return ret;
62+
}
63+
64+
/// <summary>
65+
/// Handles incoming data by invoking a callback method, if one is defined.
66+
/// </summary>
67+
/// <remarks>This method is designed to be overridden in derived classes to provide custom handling of
68+
/// incoming data. If a callback method is assigned, it will be invoked asynchronously with the provided
69+
/// data.</remarks>
70+
/// <param name="data">The incoming data to be processed, represented as a read-only memory block of bytes.</param>
71+
/// <returns></returns>
72+
protected virtual async ValueTask OnHandlerReceivedCallBack(ReadOnlyMemory<byte> data)
73+
{
74+
if (ReceivedCallBack != null)
75+
{
76+
// 调用接收回调方法处理数据
77+
await ReceivedCallBack(data);
78+
}
79+
}
80+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
namespace BootstrapBlazor.DataAdapters;
6+
7+
/// <summary>
8+
/// Defines an adapter for handling and transmitting data packages to a target destination.
9+
/// </summary>
10+
/// <remarks>This interface provides methods for sending data asynchronously and configuring a data handler.
11+
/// Implementations of this interface are responsible for managing the interaction between the caller and the underlying
12+
/// data transmission mechanism.</remarks>
13+
public interface IDataPackageAdapter
14+
{
15+
/// <summary>
16+
/// Gets or sets the callback function to be invoked when data is received.
17+
/// </summary>
18+
/// <remarks>The callback function is expected to handle the received data asynchronously. Ensure that the
19+
/// implementation of the callback does not block the calling thread and completes promptly to avoid performance
20+
/// issues.</remarks>
21+
Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack { get; set; }
22+
23+
/// <summary>
24+
/// Gets the handler responsible for processing data packages.
25+
/// </summary>
26+
IDataPackageHandler? DataPackageHandler { get; }
27+
28+
/// <summary>
29+
/// Asynchronously receives data from a source and processes it.
30+
/// </summary>
31+
/// <remarks>This method does not return any result directly. It is intended for scenarios where data is received
32+
/// and processed asynchronously. Ensure that the <paramref name="data"/> parameter contains valid data before calling
33+
/// this method.</remarks>
34+
/// <param name="data">A read-only memory region containing the data to be received. The caller must ensure the memory is valid and
35+
/// populated.</param>
36+
/// <param name="token">An optional cancellation token that can be used to cancel the operation. Defaults to <see langword="default"/> if
37+
/// not provided.</param>
38+
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation. The task completes when the data has been
39+
/// successfully received and processed.</returns>
40+
ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);
41+
42+
/// <summary>
43+
/// Attempts to convert the specified byte data into an entity of type <typeparamref name="TEntity"/>.
44+
/// </summary>
45+
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
46+
/// langword="false"/> and sets <paramref name="entity"/> to its default value.</remarks>
47+
/// <typeparam name="TEntity">The type of the entity to convert the data to.</typeparam>
48+
/// <param name="data">The byte data to be converted.</param>
49+
/// <param name="socketDataConverter">The converter used to transform the byte data into an entity.</param>
50+
/// <param name="entity">When this method returns, contains the converted entity if the conversion was successful; otherwise, the default
51+
/// value for the type of the entity.</param>
52+
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
53+
bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, IDataConverter<TEntity> socketDataConverter, out TEntity? entity);
54+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
using System.Reflection;
6+
7+
namespace BootstrapBlazor.DataConverters;
8+
9+
/// <summary>
10+
/// Provides a base class for converting socket data into a specified entity type.
11+
/// </summary>
12+
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
13+
public class DataConverter<TEntity>(DataConverterCollections converters) : IDataConverter<TEntity>
14+
{
15+
/// <summary>
16+
/// 构造函数
17+
/// </summary>
18+
public DataConverter() : this(new())
19+
{
20+
21+
}
22+
23+
/// <summary>
24+
/// <inheritdoc/>
25+
/// </summary>
26+
/// <param name="data"></param>
27+
/// <param name="entity"></param>
28+
/// <returns></returns>
29+
public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity)
30+
{
31+
var v = CreateEntity();
32+
var ret = Parse(data, v);
33+
entity = ret ? v : default;
34+
return ret;
35+
}
36+
37+
/// <summary>
38+
/// 创建实体实例方法
39+
/// </summary>
40+
/// <returns></returns>
41+
protected virtual TEntity CreateEntity() => Activator.CreateInstance<TEntity>();
42+
43+
/// <summary>
44+
/// 将字节数据转换为指定实体类型的实例。
45+
/// </summary>
46+
/// <param name="data"></param>
47+
/// <param name="entity"></param>
48+
protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
49+
{
50+
// 使用 SocketDataPropertyAttribute 特性获取数据转换规则
51+
var ret = false;
52+
if (entity != null)
53+
{
54+
var unuseProperties = new List<PropertyInfo>(32);
55+
56+
// 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器
57+
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
58+
foreach (var p in properties)
59+
{
60+
var attr = p.GetCustomAttribute<DataPropertyConverterAttribute>(false)
61+
?? GetPropertyConverterAttribute(p);
62+
if (attr != null)
63+
{
64+
p.SetValue(entity, attr.ConvertTo(data));
65+
}
66+
}
67+
ret = true;
68+
}
69+
return ret;
70+
}
71+
72+
private DataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo)
73+
{
74+
DataPropertyConverterAttribute? attr = null;
75+
if (converters.TryGetPropertyConverter<TEntity>(propertyInfo, out var v))
76+
{
77+
attr = v;
78+
}
79+
return attr;
80+
}
81+
}

0 commit comments

Comments
 (0)