Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,18 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPack
}

/// <summary>
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and a callback
/// function for processing received data.
/// Configures the specified <see cref="ITcpSocketClient"/> to use a data package adapter and a callback function
/// for processing received data.
/// </summary>
/// <remarks>This method sets up the <paramref name="client"/> to use the provided <paramref
/// name="adapter"/> for handling incoming data. The adapter processes the raw data received by the client and
/// attempts to convert it into an instance of <typeparamref name="TEntity"/>. If the conversion is successful, the
/// <paramref name="callback"/> is invoked with the converted entity; otherwise, it is invoked with <see
/// langword="null"/>.</remarks>
/// <typeparam name="TEntity">The type of the entity that the data package adapter will attempt to convert the received data into.</typeparam>
/// <param name="client">The <see cref="ITcpSocketClient"/> instance to configure.</param>
/// <param name="adapter">The <see cref="IDataPackageAdapter"/> instance responsible for handling and processing incoming data.</param>
/// <param name="callback">A callback function to be invoked with the processed data of type <typeparamref name="TEntity"/>. The callback
/// receives <see langword="null"/> if the data cannot be converted to <typeparamref name="TEntity"/>.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
/// <remarks>This method sets up the <paramref name="client"/> to process incoming data using the
/// specified <paramref name="adapter"/> and <paramref name="socketDataConverter"/>. The <paramref
/// name="callback"/> is called with the converted entity whenever data is received.</remarks>
/// <typeparam name="TEntity">The type of the entity that the data will be converted to.</typeparam>
/// <param name="client">The TCP socket client to configure.</param>
/// <param name="adapter">The data package adapter responsible for handling incoming data.</param>
/// <param name="socketDataConverter">The converter used to transform the received data into the specified entity type.</param>
/// <param name="callback">The callback function to be invoked with the converted entity.</param>
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, ISocketDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
{
// 设置 ITcpSocketClient 的回调函数
client.ReceivedCallBack = async buffer =>
Expand All @@ -102,12 +100,9 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
adapter.ReceivedCallBack = async buffer =>
{
TEntity? ret = default;
if (adapter.TryConvertTo(buffer, out var t))
if (socketDataConverter.TryConvertTo(buffer, out var t))
{
if (t is TEntity entity)
{
ret = entity;
}
ret = t;
}
await callback(ret);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Defines a method to convert raw socket data into a specified entity type.
/// </summary>
/// <typeparam name="TEntity">The type of entity to convert the data into.</typeparam>
public interface ISocketDataConverter<TEntity>
{
/// <summary>
/// Attempts to convert the specified data to an instance of <typeparamref name="TEntity"/>.
/// </summary>
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
/// langword="false"/> and sets <paramref name="entity"/> to <see langword="null"/>.</remarks>
/// <param name="data">The data to be converted, represented as a read-only memory block of bytes.</param>
/// <param name="entity">When this method returns, contains the converted <typeparamref name="TEntity"/> if the conversion succeeded;
/// otherwise, <see langword="null"/>.</param>
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Provides a base class for converting socket data into a specified entity type.
/// </summary>
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
public abstract class SocketDataConverterBase<TEntity> : ISocketDataConverter<TEntity>
{
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="data"></param>
/// <param name="entity"></param>
/// <returns></returns>
public abstract bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,17 @@ public virtual async ValueTask HandlerAsync(ReadOnlyMemory<byte> data, Cancellat
/// <inheritdoc/>
/// </summary>
/// <param name="data"></param>
/// <param name="socketDataConverter"></param>
/// <param name="entity"></param>
/// <returns></returns>
public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity)
public virtual bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, ISocketDataConverter<TEntity> socketDataConverter, out TEntity? entity)
{
entity = null;
return false;
entity = default;
if (socketDataConverter.TryConvertTo(data, out var v))
{
entity = v;
}
return entity != null;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ public interface IDataPackageAdapter
ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token = default);

/// <summary>
/// Attempts to convert the specified binary data into an object representation.
/// Attempts to convert the specified byte data into an entity of type <typeparamref name="TEntity"/>.
/// </summary>
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
/// langword="false"/> and sets <paramref name="entity"/> to <see langword="null"/>.</remarks>
/// <param name="data">The binary data to be converted. Must not be empty.</param>
/// <param name="entity">When this method returns <see langword="true"/>, contains the converted object. When this method returns <see
/// langword="false"/>, contains <see langword="null"/>.</param>
/// <remarks>This method does not throw an exception if the conversion fails. Instead, it returns <see
/// langword="false"/> and sets <paramref name="entity"/> to its default value.</remarks>
/// <typeparam name="TEntity">The type of the entity to convert the data to.</typeparam>
/// <param name="data">The byte data to be converted.</param>
/// <param name="socketDataConverter">The converter used to transform the byte data into an entity.</param>
/// <param name="entity">When this method returns, contains the converted entity if the conversion was successful; otherwise, the default
/// value for the type of the entity.</param>
/// <returns><see langword="true"/> if the conversion was successful; otherwise, <see langword="false"/>.</returns>
bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity);
bool TryConvertTo<TEntity>(ReadOnlyMemory<byte> data, ISocketDataConverter<TEntity> socketDataConverter, out TEntity? entity);
}
30 changes: 11 additions & 19 deletions test/UnitTest/Services/TcpSocketFactoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -651,11 +651,11 @@ public async Task TryConvertTo_Ok()
MockEntity? entity = null;

// 设置数据适配器
var adapter = new MockEntityDataPackageAdapter
var adapter = new DataPackageAdapter
{
DataPackageHandler = new FixLengthDataPackageHandler(7),
};
client.SetDataPackageAdapter<MockEntity>(adapter, t =>
client.SetDataPackageAdapter(adapter, new MockEntitySocketDataConverter(), t =>
{
entity = t;
tcs.SetResult();
Expand All @@ -676,9 +676,10 @@ public async Task TryConvertTo_Ok()

// 测试异常流程
var adapter2 = new DataPackageAdapter();
var result = adapter2.TryConvertTo(data, out var t);
Assert.False(result);
Assert.Null(t);
var result = adapter2.TryConvertTo(data, new MockEntitySocketDataConverter(), out var t);
Assert.True(result);
Assert.NotNull(t);
Assert.Equal([1, 2, 3, 4, 5], entity.Header);
}

[Fact]
Expand All @@ -691,11 +692,11 @@ public async Task TryConvertTo_Null()
MockEntity? entity = null;

// 设置数据适配器
var adapter = new MockErrorEntityDataPackageAdapter
var adapter = new DataPackageAdapter
{
DataPackageHandler = new FixLengthDataPackageHandler(7),
};
client.SetDataPackageAdapter<MockEntity>(adapter, t =>
client.SetDataPackageAdapter(adapter, new MockEntitySocketDataConverter(), t =>
{
entity = t;
tcs.SetResult();
Expand All @@ -710,7 +711,7 @@ public async Task TryConvertTo_Null()
await client.SendAsync(data);
await tcs.Task;

Assert.Null(entity);
Assert.NotNull(entity);
}

private static TcpListener StartTcpServer(int port, Func<TcpClient, Task> handler)
Expand Down Expand Up @@ -1080,9 +1081,9 @@ public void SetReceive(bool state)
}
}

class MockEntityDataPackageAdapter : DataPackageAdapter
class MockEntitySocketDataConverter : SocketDataConverterBase<MockEntity>
{
public override bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity)
public override bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out MockEntity? entity)
{
entity = new MockEntity
{
Expand All @@ -1093,15 +1094,6 @@ public override bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)]
}
}

class MockErrorEntityDataPackageAdapter : DataPackageAdapter
{
public override bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out object? entity)
{
entity = new Foo();
return true;
}
}

class MockEntity
{
public byte[]? Header { get; set; }
Expand Down
Loading