diff --git a/src/BootstrapBlazor.Server/Components/Pages/Introduction.razor b/src/BootstrapBlazor.Server/Components/Pages/Introduction.razor
index f837ef9ca92..27956981a48 100644
--- a/src/BootstrapBlazor.Server/Components/Pages/Introduction.razor
+++ b/src/BootstrapBlazor.Server/Components/Pages/Introduction.razor
@@ -1,5 +1,6 @@
@page "/docs"
@page "/introduction"
+@page "/components"
@Localizer["Title"]
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs
index 0532684ddcd..4baa2bf2d1e 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/Adapters.razor.cs
@@ -93,7 +93,7 @@ private async ValueTask OnReceivedAsync(ReadOnlyMemory data)
if (_useDataAdapter)
{
// 使用数据适配器处理接收的数据
- await _dataAdapter.ReceiveAsync(data, _receiveTokenSource.Token);
+ await _dataAdapter.HandlerAsync(data, _receiveTokenSource.Token);
}
else
{
diff --git a/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs b/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
index 785afffb34a..cf58a2b31ad 100644
--- a/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
+++ b/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
@@ -48,4 +48,68 @@ public static ValueTask ConnectAsync(this ITcpSocketClient client, string
var endPoint = Utility.ConvertToIpEndPoint(ipString, port);
return client.ConnectAsync(endPoint, token);
}
+
+ ///
+ /// Configures the specified to use the provided
+ /// for processing received data and sets a callback to handle processed data.
+ ///
+ /// This method sets up a two-way data processing pipeline: -
+ /// The is configured to pass received data to the
+ /// for processing.
- The is configured to invoke
+ /// the provided with the processed data.
Use this method
+ /// to integrate a custom data processing adapter with a TCP socket client.
+ /// The instance to configure.
+ /// The used to process incoming data.
+ /// A callback function invoked with the processed data. The function receives a
+ /// containing the processed data and returns a .
+ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func, ValueTask> callback)
+ {
+ // 设置 ITcpSocketClient 的回调函数
+ client.ReceivedCallBack = async buffer =>
+ {
+ // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
+ await adapter.HandlerAsync(buffer);
+ };
+
+ // 设置 DataPackageAdapter 的回调函数
+ adapter.ReceivedCallBack = buffer => callback(buffer);
+ }
+
+ ///
+ /// Configures the specified to use a custom data package adapter and a callback
+ /// function for processing received data.
+ ///
+ /// This method sets up the to use the provided for handling incoming data. The adapter processes the raw data received by the client and
+ /// attempts to convert it into an instance of . If the conversion is successful, the
+ /// is invoked with the converted entity; otherwise, it is invoked with .
+ /// The type of the entity that the data package adapter will attempt to convert the received data into.
+ /// The instance to configure.
+ /// The instance responsible for handling and processing incoming data.
+ /// A callback function to be invoked with the processed data of type . The callback
+ /// receives if the data cannot be converted to .
+ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func callback)
+ {
+ // 设置 ITcpSocketClient 的回调函数
+ client.ReceivedCallBack = async buffer =>
+ {
+ // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
+ await adapter.HandlerAsync(buffer);
+ };
+
+ // 设置 DataPackageAdapter 的回调函数
+ adapter.ReceivedCallBack = async buffer =>
+ {
+ TEntity? ret = default;
+ if (adapter.TryConvertTo(buffer, out var t))
+ {
+ if (t is TEntity entity)
+ {
+ ret = entity;
+ }
+ }
+ await callback(ret);
+ };
+ }
}
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs
index d19f2736871..a1b7e98e940 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageAdapter.cs
@@ -30,7 +30,7 @@ public class DataPackageAdapter : IDataPackageAdapter
///
///
///
- public virtual async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default)
+ public virtual async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default)
{
if (DataPackageHandler != null)
{
@@ -40,10 +40,22 @@ public virtual async ValueTask ReceiveAsync(ReadOnlyMemory data, Cancellat
}
// 如果存在数据处理器则调用其处理方法
- await DataPackageHandler.ReceiveAsync(data, token);
+ await DataPackageHandler.HandlerAsync(data, token);
}
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity)
+ {
+ entity = null;
+ return false;
+ }
+
///
/// Handles incoming data by invoking a callback method, if one is defined.
///
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs
index 89a760fa93a..41677e72a7b 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DataPackageHandlerBase.cs
@@ -26,7 +26,7 @@ public abstract class DataPackageHandlerBase : IDataPackageHandler
///
///
///
- public abstract ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default);
+ public abstract ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default);
///
/// Handles the processing of a sticky package by adjusting the provided buffer and length.
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DelimiterDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DelimiterDataPackageHandler.cs
index 1eee4fdf3ed..e8923643006 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DelimiterDataPackageHandler.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/DelimiterDataPackageHandler.cs
@@ -52,7 +52,7 @@ public DelimiterDataPackageHandler(byte[] delimiter)
///
///
///
- public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default)
+ public override async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default)
{
data = ConcatBuffer(data);
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs
index 0661a8295a8..40289ace4ff 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/FixLengthDataPackageHandler.cs
@@ -24,7 +24,7 @@ public class FixLengthDataPackageHandler(int length) : DataPackageHandlerBase
///
///
///
- public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default)
+ public override async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default)
{
while (data.Length > 0)
{
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs
index f91aa15ecca..f603c528460 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageAdapter.cs
@@ -38,5 +38,16 @@ public interface IDataPackageAdapter
/// not provided.
/// A representing the asynchronous operation. The task completes when the data has been
/// successfully received and processed.
- ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default);
+ ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default);
+
+ ///
+ /// Attempts to convert the specified binary data into an object representation.
+ ///
+ /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to .
+ /// The binary data to be converted. Must not be empty.
+ /// When this method returns , contains the converted object. When this method returns , contains .
+ /// if the conversion was successful; otherwise, .
+ bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity);
}
diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs
index 74da06af81b..25dbd85f445 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackage/IDataPackageHandler.cs
@@ -29,5 +29,5 @@ public interface IDataPackageHandler
/// provided.
/// A containing if the data was successfully received and
/// processed; otherwise, .
- ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default);
+ ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default);
}
diff --git a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
index 5329eec3549..8c5134c8528 100644
--- a/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
+++ b/src/BootstrapBlazor/Services/TcpSocket/TcpSocketClientBase.cs
@@ -148,9 +148,14 @@ public virtual async ValueTask SendAsync(ReadOnlyMemory data, Cancel
}
catch (OperationCanceledException ex)
{
- Log(LogLevel.Warning, ex, token.IsCancellationRequested
- ? $"TCP Socket send operation was canceled from {_localEndPoint} to {_remoteEndPoint}"
- : $"TCP Socket send operation timed out from {_localEndPoint} to {_remoteEndPoint}");
+ if (token.IsCancellationRequested)
+ {
+ Log(LogLevel.Warning, ex, $"TCP Socket send operation was canceled from {_localEndPoint} to {_remoteEndPoint}");
+ }
+ else
+ {
+ Log(LogLevel.Warning, ex, $"TCP Socket send operation timed out from {_localEndPoint} to {_remoteEndPoint}");
+ }
}
catch (Exception ex)
{
diff --git a/test/UnitTest/Services/TcpSocketFactoryTest.cs b/test/UnitTest/Services/TcpSocketFactoryTest.cs
index d38e1d4544a..1f7ccb317ae 100644
--- a/test/UnitTest/Services/TcpSocketFactoryTest.cs
+++ b/test/UnitTest/Services/TcpSocketFactoryTest.cs
@@ -308,22 +308,16 @@ public async Task FixLengthDataPackageHandler_Ok()
// 设置数据适配器
var adapter = new DataPackageAdapter
{
- DataPackageHandler = new FixLengthDataPackageHandler(7),
- ReceivedCallBack = buffer =>
- {
- // buffer 即是接收到的数据
- buffer.CopyTo(receivedBuffer);
- receivedBuffer = receivedBuffer[..buffer.Length];
- tcs.SetResult();
- return ValueTask.CompletedTask;
- }
+ DataPackageHandler = new FixLengthDataPackageHandler(7)
};
-
- client.ReceivedCallBack = async buffer =>
+ client.SetDataPackageAdapter(adapter, buffer =>
{
- // 将接收到的数据传递给 DataPackageAdapter
- await adapter.ReceiveAsync(buffer);
- };
+ // buffer 即是接收到的数据
+ buffer.CopyTo(receivedBuffer);
+ receivedBuffer = receivedBuffer[..buffer.Length];
+ tcs.SetResult();
+ return ValueTask.CompletedTask;
+ });
// 测试 ConnectAsync 方法
var connect = await client.ConnectAsync("localhost", port);
@@ -350,7 +344,7 @@ public async Task FixLengthDataPackageHandler_Sticky()
var server = StartTcpServer(port, MockStickyPackageAsync);
var client = CreateClient();
var tcs = new TaskCompletionSource();
- var receivedBuffer = new byte[1024];
+ var receivedBuffer = new byte[128];
// 连接 TCP Server
var connect = await client.ConnectAsync("localhost", port);
@@ -358,22 +352,17 @@ public async Task FixLengthDataPackageHandler_Sticky()
// 设置数据适配器
var adapter = new DataPackageAdapter
{
- DataPackageHandler = new FixLengthDataPackageHandler(7),
- ReceivedCallBack = buffer =>
- {
- // buffer 即是接收到的数据
- buffer.CopyTo(receivedBuffer);
- receivedBuffer = receivedBuffer[..buffer.Length];
- tcs.SetResult();
- return ValueTask.CompletedTask;
- }
+ DataPackageHandler = new FixLengthDataPackageHandler(7)
};
- client.ReceivedCallBack = async buffer =>
+ client.SetDataPackageAdapter(adapter, buffer =>
{
- // 将接收到的数据传递给 DataPackageAdapter
- await adapter.ReceiveAsync(buffer);
- };
+ // buffer 即是接收到的数据
+ buffer.CopyTo(receivedBuffer);
+ receivedBuffer = receivedBuffer[..buffer.Length];
+ tcs.SetResult();
+ return ValueTask.CompletedTask;
+ });
// 发送数据
var data = new ReadOnlyMemory([1, 2, 3, 4, 5]);
@@ -408,31 +397,25 @@ public async Task FixLengthDataPackageHandler_Sticky()
[Fact]
public async Task DelimiterDataPackageHandler_Ok()
{
- var port = 8886;
+ var port = 8883;
var server = StartTcpServer(port, MockDelimiterPackageAsync);
var client = CreateClient();
var tcs = new TaskCompletionSource();
- var receivedBuffer = new byte[1024];
+ var receivedBuffer = new byte[128];
// 设置数据适配器
var adapter = new DataPackageAdapter
{
- DataPackageHandler = new DelimiterDataPackageHandler(new byte[] { 13, 10 }),
- ReceivedCallBack = buffer =>
- {
- // buffer 即是接收到的数据
- buffer.CopyTo(receivedBuffer);
- receivedBuffer = receivedBuffer[..buffer.Length];
- tcs.SetResult();
- return ValueTask.CompletedTask;
- }
+ DataPackageHandler = new DelimiterDataPackageHandler([13, 10]),
};
-
- client.ReceivedCallBack = async buffer =>
+ client.SetDataPackageAdapter(adapter, buffer =>
{
- // 将接收到的数据传递给 DataPackageAdapter
- await adapter.ReceiveAsync(buffer);
- };
+ // buffer 即是接收到的数据
+ buffer.CopyTo(receivedBuffer);
+ receivedBuffer = receivedBuffer[..buffer.Length];
+ tcs.SetResult();
+ return ValueTask.CompletedTask;
+ });
// 连接 TCP Server
var connect = await client.ConnectAsync("localhost", port);
@@ -467,6 +450,78 @@ public async Task DelimiterDataPackageHandler_Ok()
Assert.NotNull(ex);
}
+ [Fact]
+ public async Task TryConvertTo_Ok()
+ {
+ var port = 8886;
+ var server = StartTcpServer(port, MockSplitPackageAsync);
+ var client = CreateClient();
+ var tcs = new TaskCompletionSource();
+ MockEntity? entity = null;
+
+ // 设置数据适配器
+ var adapter = new MockEntityDataPackageAdapter
+ {
+ DataPackageHandler = new FixLengthDataPackageHandler(7),
+ };
+ client.SetDataPackageAdapter(adapter, t =>
+ {
+ entity = t;
+ tcs.SetResult();
+ return Task.CompletedTask;
+ });
+
+ // 连接 TCP Server
+ var connect = await client.ConnectAsync("localhost", port);
+
+ // 发送数据
+ var data = new ReadOnlyMemory([1, 2, 3, 4, 5]);
+ await client.SendAsync(data);
+ await tcs.Task;
+
+ Assert.NotNull(entity);
+ Assert.Equal(entity.Header, [1, 2, 3, 4, 5]);
+ Assert.Equal(entity.Body, [3, 4]);
+
+ // 测试异常流程
+ var adapter2 = new DataPackageAdapter();
+ var result = adapter2.TryConvertTo(data, out var t);
+ Assert.False(result);
+ Assert.Null(t);
+ }
+
+ [Fact]
+ public async Task TryConvertTo_Null()
+ {
+ var port = 8890;
+ var server = StartTcpServer(port, MockSplitPackageAsync);
+ var client = CreateClient();
+ var tcs = new TaskCompletionSource();
+ MockEntity? entity = null;
+
+ // 设置数据适配器
+ var adapter = new MockErrorEntityDataPackageAdapter
+ {
+ DataPackageHandler = new FixLengthDataPackageHandler(7),
+ };
+ client.SetDataPackageAdapter(adapter, t =>
+ {
+ entity = t;
+ tcs.SetResult();
+ return Task.CompletedTask;
+ });
+
+ // 连接 TCP Server
+ var connect = await client.ConnectAsync("localhost", port);
+
+ // 发送数据
+ var data = new ReadOnlyMemory([1, 2, 3, 4, 5]);
+ await client.SendAsync(data);
+ await tcs.Task;
+
+ Assert.Null(entity);
+ }
+
private static TcpListener StartTcpServer(int port, Func handler)
{
var server = new TcpListener(IPAddress.Loopback, port);
@@ -574,10 +629,8 @@ private static ITcpSocketClient CreateClient(Action? builder
builder.AddProvider(new MockLoggerProvider());
});
sc.AddBootstrapBlazorTcpSocketFactory();
- if (builder != null)
- {
- builder(sc);
- }
+ builder?.Invoke(sc);
+
var provider = sc.BuildServiceProvider();
var factory = provider.GetRequiredService();
var client = factory.GetOrCreate("test", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0));
@@ -619,7 +672,7 @@ class MockSendErrorSocketProvider : ISocketClientProvider
{
public bool IsConnected { get; private set; }
- public IPEndPoint LocalEndPoint { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
public ValueTask CloseAsync()
{
@@ -647,7 +700,7 @@ class MockSendTimeoutSocketProvider : ISocketClientProvider
{
public bool IsConnected { get; private set; }
- public IPEndPoint LocalEndPoint { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
public ValueTask CloseAsync()
{
@@ -672,4 +725,33 @@ public async ValueTask SendAsync(ReadOnlyMemory data, CancellationTo
return false;
}
}
+
+ class MockEntityDataPackageAdapter : DataPackageAdapter
+ {
+ public override bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity)
+ {
+ entity = new MockEntity
+ {
+ Header = data[..5].ToArray(),
+ Body = data[5..].ToArray()
+ };
+ return true;
+ }
+ }
+
+ class MockErrorEntityDataPackageAdapter : DataPackageAdapter
+ {
+ public override bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity)
+ {
+ entity = new Foo();
+ return true;
+ }
+ }
+
+ class MockEntity
+ {
+ public byte[]? Header { get; set; }
+
+ public byte[]? Body { get; set; }
+ }
}