diff --git a/src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor b/src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor index e06b1682265..58b8bfd2760 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/SocketFactories.razor @@ -84,22 +84,22 @@ private async Task CreateClient()
数据适配器设计思路如下
SocketDataConverterAttribute 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 ISocketDataConverter
+ SocketDataTypeConverterAttribute 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 ISocketDataConverter
接口
SocketDataPropertyAttribute 标签约定如何转换数据类型 (Property) 属性值SocketDataPropertyConverterAttribute 标签约定如何转换数据类型 (Property) 属性值[SocketDataConverter(Type = typeof(SocketDataConverter<MockEntity>))] +[SocketDataTypeConverter(Type = typeof(SocketDataConverter<MockEntity>))] class MockEntity { - [SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] public byte[]? Header { get; set; } - [SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] public byte[]? Body { get; set; } - [SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] + [SocketDataPropertyConverter(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] public string? Value1 { get; set; } }diff --git a/src/BootstrapBlazor.Server/Components/Samples/Sockets/DataEntities.razor b/src/BootstrapBlazor.Server/Components/Samples/Sockets/DataEntities.razor index abc9669f65a..f42a04f24c7 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Sockets/DataEntities.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/Sockets/DataEntities.razor @@ -7,62 +7,30 @@- 通过
SocketDataConverterAttribute类标签与SocketDataPropertyAttribute++ 通过
-SocketDataTypeConverterAttribute类标签与SocketDataPropertyConverterAttribute属性标签可以将通讯数据自动转化为我们系统中需要的业务实体类,示例如下:[SocketDataConverter(Type = typeof(SocketDataConverter<MockEntity>))] +[SocketDataTypeConverter(Type = typeof(SocketDataConverter<MockEntity>))] class MockEntity { - [SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] public byte[]? Header { get; set; } - [SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] public byte[]? Body { get; set; } - [SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] + [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] public string? Value1 { get; set; } - [SocketDataProperty(Type = typeof(int), Offset = 8, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)] public int Value2 { get; set; } }-1.
+SocketDataConverter参数说明:1.
SocketDataTypeConverter参数说明:-
- Type: 自定义转换器类型,组件库内置了
SocketDataConverter泛型类,建议看一下源码非常方便扩展出自己的转换器class MockEntitySocketDataConverter : SocketDataConverterBase<MockEntity> -{ - public override bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out MockEntity? entity) - { - var v = new MockEntity(); - - // 调用基类的 Parse 方法即可 - var ret = Parse(data, v); - entity = ret ? v : null; - return ret; - } -}--
Parse方法也是可以重载自定义实现的,内置实现逻辑如下:protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity) -{ - // 使用 SocketDataPropertyAttribute 特性获取数据转换规则 - var ret = false; - if (entity != null) - { - var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList(); - foreach (var p in properties) - { - var attr = p.GetCustomAttribute<SocketDataPropertyAttribute>(false); - if (attr != null) - { - p.SetValue(entity, attr.ConvertTo(data)); - } - } - ret = true; - } - return ret; -}-即遍历所有有
-SocketDataPropertyAttribute标签的属性对其进行过自动赋值2.
+SocketDataPropertyAttribute参数说明2.
SocketDataPropertyConverterAttribute参数说明
- Type: 转换目标数据类型
- Offset: 数据偏移量,即在接收到的数据中起始位置
@@ -95,16 +63,16 @@ class MockEntitySocketDataUInt64LittleEndianConverter转成 ulong 无符号整形小端读取自定义数据类型转化器示例
-[SocketDataConverter(Type = typeof(SocketDataConverter<MockEntity>))] +[SocketTypeDataConverter(Type = typeof(SocketDataConverter<MockEntity>))] class MockEntity { - [SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] public byte[]? Header { get; set; } - [SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] public byte[]? Body { get; set; } - [SocketDataProperty(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] + [SocketDataPropertyConverter(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] public string? Value1 { get; set; } }class FooConverter(string name) : ISocketDataPropertyConverter diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json index 612f08e7996..57bc7a42645 100644 --- a/src/BootstrapBlazor.Server/Locales/en-US.json +++ b/src/BootstrapBlazor.Server/Locales/en-US.json @@ -7129,7 +7129,7 @@ "DataEntityTitle": "Socket Auto Entity", "DataEntityDescription": "After receiving the communication data, it is automatically converted into the entity class required by the business", "NormalTitle": "Basic usage", - "NormalIntro": "Enable automatic data conversion through theSocketDataConverterAttributetag" + "NormalIntro": "Enable automatic data conversion through theSocketDataTypeConverterAttributeattribute" }, "BootstrapBlazor.Server.Components.Samples.NetworkMonitors": { "NetworkMonitorTitle": "NetworkMonitor", diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json index 61a1c4dba1a..c4a0301e360 100644 --- a/src/BootstrapBlazor.Server/Locales/zh-CN.json +++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json @@ -7129,7 +7129,7 @@ "DataEntityTitle": "Socket 数据转化实体类", "DataEntityDescription": "接收到通讯数据后自动转成业务需要的实体类", "NormalTitle": "基本用法", - "NormalIntro": "通过SocketDataConverterAttribute标签开启数据自动转换功能" + "NormalIntro": "通过SocketDataTypeConverterAttribute标签开启数据自动转换功能" }, "BootstrapBlazor.Server.Components.Samples.NetworkMonitors": { "NetworkMonitorTitle": "NetworkMonitor 网络状态", diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs index bf57e690b9f..e18ce3df56f 100644 --- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs +++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs @@ -117,6 +117,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv services.AddTabItemBindOptions(); services.AddIconTheme(); + services.AddSocketDataConverters(); return services; } @@ -212,7 +213,30 @@ static IServiceCollection AddTabItemBindOptions(this IServiceCollection services } ///- /// 增加第三方菜单路由与 Tab 捆绑字典配置 + /// 增加 Socket 数据转换器集合配置项服务 + /// + /// + ///+ static IServiceCollection AddSocketDataConverters(this IServiceCollection services) + { + services.AddOptionsMonitor (); + return services; + } + + /// + /// 配置第三方数据模型与 + /// + /// + ///数据转换器集合配置扩展方法 + /// + public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action configureOptions) + { + services.Configure(configureOptions); + return services; + } + + /// + /// 配置第三方菜单路由与 Tab 标签页捆绑字典扩展方法 /// /// /// @@ -236,7 +260,7 @@ static IServiceCollection AddIconTheme(this IServiceCollection services) } ///- /// IconThemeOptions 扩展配置方法 + /// 配置 /// /// diff --git a/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs b/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs index 9fadf2ab442..ccd03c1d31e 100644 --- a/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone +using Microsoft.Extensions.DependencyInjection; using System.Reflection; using System.Runtime.Versioning; using System.Text; @@ -115,7 +116,7 @@ public static void SetDataPackageAdapter扩展方法 /// (this ITcpSocketClient client, /// /// This method sets up the ///to use the specified for handling incoming data. If the type is decorated with a , the associated converter is used to transform the data before invoking + /// cref="SocketDataTypeConverterAttribute"/>, the associated converter is used to transform the data before invoking /// the . The callback is called with the converted entity or if /// conversion fails. The type of entity that the data package adapter will handle. @@ -131,27 +132,60 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, await adapter.HandlerAsync(buffer); }; + ISocketDataConverter ? converter = null; + var type = typeof(TEntity); - var converterType = type.GetCustomAttribute (); + var converterType = type.GetCustomAttribute (); if (converterType is { Type: not null }) { + // 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器 if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter socketDataConverter) { - // 设置 DataPackageAdapter 的回调函数 - adapter.ReceivedCallBack = async buffer => - { - TEntity? ret = default; - if (socketDataConverter.TryConvertTo(buffer, out var t)) - { - ret = t; - } - await callback(ret); - }; + converter = socketDataConverter; } } else { + // 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器 + converter = client.GetSocketDataConverter (); + } + + if (converter == null) + { + // 设置正常回调 adapter.ReceivedCallBack = async buffer => await callback(default); } + else + { + // 设置转化器 + adapter.SetDataAdapterCallback(converter, callback); + } + } + + private static void SetDataAdapterCallback (this IDataPackageAdapter adapter, ISocketDataConverter converter, Func callback) + { + adapter.ReceivedCallBack = async buffer => + { + TEntity? ret = default; + if (converter.TryConvertTo(buffer, out var t)) + { + ret = t; + } + await callback(ret); + }; + } + + private static ISocketDataConverter ? GetSocketDataConverter (this ITcpSocketClient client) + { + ISocketDataConverter ? converter = null; + if (client is IServiceProvider provider) + { + var converters = provider.GetRequiredService >().Value; + if (converters.TryGetTypeConverter (out var v)) + { + converter = v; + } + } + return converter; } } diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs index 1d5f3f6b0d7..aaf31b557e7 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs @@ -5,11 +5,19 @@ namespace BootstrapBlazor.Components; +/// +/// Socket 数据转换器接口 +/// +public interface ISocketDataConverter +{ + +} + ////// Defines a method to convert raw socket data into a specified entity type. /// ///The type of entity to convert the data into. -public interface ISocketDataConverter+public interface ISocketDataConverter : ISocketDataConverter { /// /// Attempts to convert the specified data to an instance of ///. diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverter.cs index 642cac45870..324da0cafa2 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverter.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverter.cs @@ -11,8 +11,16 @@ namespace BootstrapBlazor.Components; /// Provides a base class for converting socket data into a specified entity type. /// The type of entity to convert the socket data into. -public class SocketDataConverter: ISocketDataConverter +public class SocketDataConverter (SocketDataConverterCollections converters) : ISocketDataConverter { + /// + /// 构造函数 + /// + public SocketDataConverter() : this(new()) + { + + } + ////// @@ -44,10 +52,14 @@ protected virtual bool Parse(ReadOnlyMemory/// data, TEntity entity) var ret = false; if (entity != null) { + var unuseProperties = new List (32); + + // 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器 var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList(); foreach (var p in properties) { - var attr = p.GetCustomAttribute (false); + var attr = p.GetCustomAttribute (false) + ?? GetPropertyConverterAttribute(p); if (attr != null) { p.SetValue(entity, attr.ConvertTo(data)); @@ -57,4 +69,14 @@ protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) } return ret; } + + private SocketDataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo) + { + SocketDataPropertyConverterAttribute? attr = null; + if (converters.TryGetPropertyConverter (propertyInfo, out var v)) + { + attr = v; + } + return attr; + } } diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterCollections.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterCollections.cs new file mode 100644 index 00000000000..71891e2a0ab --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterCollections.cs @@ -0,0 +1,96 @@ +// 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(argo@live.ca) Website: https://www.blazor.zone + +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace BootstrapBlazor.Components; + +/// +/// +/// +public class SocketDataConverterCollections +{ + readonly ConcurrentDictionary_converters = new(); + readonly ConcurrentDictionary _propertyConverters = new(); + + /// + /// 增加数据类型转换器方法 + /// + ///+ /// + public void AddOrUpdateTypeConverter (ISocketDataConverter converter) + { + var type = typeof(TEntity); + _converters.AddOrUpdate(type, t => converter, (t, v) => converter); + } + + /// + /// 添加属性类型转化器方法 + /// + ///+ /// + /// + public void AddOrUpdatePropertyConverter (Expression > propertyExpression, SocketDataPropertyConverterAttribute attribute) + { + if (propertyExpression.Body is MemberExpression memberExpression) + { + if(attribute.Type == null) + { + attribute.Type = memberExpression.Type; + } + _propertyConverters.AddOrUpdate(memberExpression.Member, m => attribute, (m, v) => attribute); + } + } + + /// + /// 获得指定数据类型转换器方法 + /// + ///+ public bool TryGetTypeConverter ([NotNullWhen(true)] out ISocketDataConverter ? converter) + { + converter = null; + var ret = false; + if (_converters.TryGetValue(typeof(TEntity), out var v) && v is ISocketDataConverter c) + { + converter = c; + ret = true; + } + return ret; + } + + /// + /// 获得指定数据类型属性转换器方法 + /// + ///+ public bool TryGetPropertyConverter (Expression > propertyExpression, [NotNullWhen(true)] out SocketDataPropertyConverterAttribute? converterAttribute) + { + converterAttribute = null; + var ret = false; + if (propertyExpression.Body is MemberExpression memberExpression && TryGetPropertyConverter (memberExpression.Member, out var v)) + { + converterAttribute = v; + ret = true; + } + return ret; + } + + /// + /// 获得指定数据类型属性转换器方法 + /// + ///+ public bool TryGetPropertyConverter (MemberInfo memberInfo, [NotNullWhen(true)] out SocketDataPropertyConverterAttribute? converterAttribute) + { + converterAttribute = null; + var ret = false; + if (_propertyConverters.TryGetValue(memberInfo, out var v)) + { + converterAttribute = v; + ret = true; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterAttribute.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataTypeConverterAttribute.cs similarity index 90% rename from src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterAttribute.cs rename to src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataTypeConverterAttribute.cs index 4a44475bc53..61b33a1ed31 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterAttribute.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataTypeConverterAttribute.cs @@ -9,7 +9,7 @@ namespace BootstrapBlazor.Components; /// /// [AttributeUsage(AttributeTargets.Class)] -public class SocketDataConverterAttribute : Attribute +public class SocketDataTypeConverterAttribute : Attribute { /// /// Gets or sets the type of the . diff --git a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs index dbbb863d05d..8c84d7a538f 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DefaultTcpSocketClient.cs @@ -12,7 +12,7 @@ namespace BootstrapBlazor.Components; [UnsupportedOSPlatform("browser")] -class DefaultTcpSocketClient(SocketClientOptions options) : ITcpSocketClient +class DefaultTcpSocketClient(SocketClientOptions options) : IServiceProvider, ITcpSocketClient { /// /// Gets or sets the socket client provider used for managing socket connections. @@ -27,6 +27,7 @@ class DefaultTcpSocketClient(SocketClientOptions options) : ITcpSocketClient /// /// Gets or sets the service provider used to resolve dependencies. /// + [NotNull] public IServiceProvider? ServiceProvider { get; set; } ///@@ -406,6 +407,13 @@ private async ValueTask CloseCoreAsync() } } + /// + /// + /// + ///+ /// + public object? GetService(Type serviceType) => ServiceProvider.GetService(serviceType); + /// /// Releases the resources used by the current instance of the class. /// diff --git a/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs b/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs index f24d5e02580..dbfe1f7f4a3 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs @@ -7,12 +7,12 @@ namespace BootstrapBlazor.Components; static class SocketDataPropertyExtensions { - public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyAttribute attribute) + public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyConverterAttribute attribute) { return attribute.GetConverterByType() ?? attribute.GetDefaultConverter(); } - private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyAttribute attribute) + private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyConverterAttribute attribute) { ISocketDataPropertyConverter? converter = null; var converterType = attribute.ConverterType; @@ -28,7 +28,7 @@ static class SocketDataPropertyExtensions return converter; } - private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyAttribute attribute) + private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyConverterAttribute attribute) { ISocketDataPropertyConverter? converter = null; var type = attribute.Type; @@ -86,7 +86,7 @@ static class SocketDataPropertyExtensions return converter; } - public static object? ConvertTo(this SocketDataPropertyAttribute attribute, ReadOnlyMemorydata) + public static object? ConvertTo(this SocketDataPropertyConverterAttribute attribute, ReadOnlyMemory data) { object? ret = null; var start = attribute.Offset; diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyAttribute.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyConverterAttribute.cs similarity index 91% rename from src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyAttribute.cs rename to src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyConverterAttribute.cs index f766b404f14..a8f171e5fd3 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyAttribute.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyConverterAttribute.cs @@ -11,8 +11,8 @@ namespace BootstrapBlazor.Components; /// This attribute can be applied to fields to indicate that they are part of the data transmitted over a /// socket connection. It is intended for use in scenarios where socket communication requires specific fields to be /// identified for processing. -[AttributeUsage(AttributeTargets.Property)] -public class SocketDataPropertyAttribute : Attribute +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class SocketDataPropertyConverterAttribute : Attribute { ////// 获得/设置 数据类型 diff --git a/test/UnitTest/Services/SocketDataConverterCollectionsTest.cs b/test/UnitTest/Services/SocketDataConverterCollectionsTest.cs new file mode 100644 index 00000000000..5f923af1014 --- /dev/null +++ b/test/UnitTest/Services/SocketDataConverterCollectionsTest.cs @@ -0,0 +1,105 @@ +// 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(argo@live.ca) Website: https://www.blazor.zone + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace UnitTest.Services; + +public class SocketDataConverterCollectionsTest : BootstrapBlazorTestBase +{ + protected override void ConfigureConfiguration(IServiceCollection services) + { + base.ConfigureConfiguration(services); + + services.ConfigureSocketDataConverters(options => + { + options.AddOrUpdateTypeConverter(new SocketDataConverter ()); + options.AddOrUpdatePropertyConverter (entity => entity.Header, new SocketDataPropertyConverterAttribute() + { + Offset = 0, + Length = 5 + }); + options.AddOrUpdatePropertyConverter (entity => entity.Body, new SocketDataPropertyConverterAttribute() + { + Offset = 5, + Length = 2 + }); + + // 为提高代码覆盖率 重复添加转换器以后面的为准 + options.AddOrUpdateTypeConverter(new SocketDataConverter ()); + options.AddOrUpdatePropertyConverter (entity => entity.Header, new SocketDataPropertyConverterAttribute() + { + Offset = 0, + Length = 5 + }); + }); + } + + [Fact] + public void TryGetConverter_Ok() + { + var service = Context.Services.GetRequiredService >(); + Assert.NotNull(service.Value); + + var ret = service.Value.TryGetTypeConverter (out var converter); + Assert.True(ret); + Assert.NotNull(converter); + + var fakeConverter = service.Value.TryGetTypeConverter (out var fooConverter); + Assert.False(fakeConverter); + Assert.Null(fooConverter); + + ret = service.Value.TryGetPropertyConverter (entity => entity.Header, out var propertyConverterAttribute); + Assert.True(ret); + Assert.NotNull(propertyConverterAttribute); + Assert.True(propertyConverterAttribute is { Offset: 0, Length: 5 }); + + ret = service.Value.TryGetPropertyConverter (entity => entity.Name, out var fooPropertyConverterAttribute); + Assert.False(ret); + Assert.Null(fooPropertyConverterAttribute); + + ret = service.Value.TryGetPropertyConverter (entity => entity.ToString(), out _); + Assert.False(ret); + } + + class MockEntity + { + public byte[]? Header { get; set; } + + public byte[]? Body { get; set; } + } + + class MockLoggerProvider : ILoggerProvider + { + public ILogger CreateLogger(string categoryName) + { + return new MockLogger(); + } + + public void Dispose() + { + + } + } + + class MockLogger : ILogger + { + public IDisposable? BeginScope (TState state) where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log (LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + + } + } +} diff --git a/test/UnitTest/Services/TcpSocketFactoryTest.cs b/test/UnitTest/Services/TcpSocketFactoryTest.cs index 740d723558b..93e36e8c55c 100644 --- a/test/UnitTest/Services/TcpSocketFactoryTest.cs +++ b/test/UnitTest/Services/TcpSocketFactoryTest.cs @@ -755,6 +755,66 @@ public async Task TryConvertTo_Ok() var converter = new MockSocketDataConverter(); result = converter.TryConvertTo(new byte[] { 0x1, 0x2 }, out t); Assert.False(result); + + server.Stop(); + } + + [Fact] + public async Task TryGetTypeConverter_Ok() + { + // 测试服务配置转换器 + var port = 8895; + var server = StartTcpServer(port, MockSplitPackageAsync); + + var client = CreateClient(builder => + { + builder.ConfigureSocketDataConverters(options => + { + options.AddOrUpdateTypeConverter(new SocketDataConverter (options)); + options.AddOrUpdatePropertyConverter (entity => entity.Header, new SocketDataPropertyConverterAttribute() + { + Offset = 0, + Length = 5 + }); + options.AddOrUpdatePropertyConverter (entity => entity.Body, new SocketDataPropertyConverterAttribute() + { + Offset = 5, + Length = 2 + }); + }); + }); + var tcs = new TaskCompletionSource(); + var receivedBuffer = new byte[128]; + + // 连接 TCP Server + var connect = await client.ConnectAsync("localhost", port); + + // 设置数据适配器 + var adapter = new DataPackageAdapter + { + DataPackageHandler = new FixLengthDataPackageHandler(7) + }; + + OptionConvertEntity? entity = null; + client.SetDataPackageAdapter (adapter, data => + { + // buffer 即是接收到的数据 + entity = data; + tcs.SetResult(); + return Task.CompletedTask; + }); + + // 发送数据 + var data = new ReadOnlyMemory ([1, 2, 3, 4, 5]); + await client.SendAsync(data); + + // 等待接收数据处理完成 + await tcs.Task; + Assert.NotNull(entity); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + Assert.Equal([3, 4], entity.Body); + + server.Stop(); } private static TcpListener StartTcpServer(int port, Func handler) @@ -1141,58 +1201,58 @@ public void SetReceive(bool state) } } - [SocketDataConverter(Type = typeof(SocketDataConverter ))] + [SocketDataTypeConverter(Type = typeof(SocketDataConverter ))] class MockEntity { - [SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] public byte[]? Header { get; set; } - [SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)] + [SocketDataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] public byte[]? Body { get; set; } - [SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] + [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] public string? Value1 { get; set; } - [SocketDataProperty(Type = typeof(int), Offset = 8, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)] public int Value2 { get; set; } - [SocketDataProperty(Type = typeof(long), Offset = 9, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(long), Offset = 9, Length = 1)] public long Value3 { get; set; } - [SocketDataProperty(Type = typeof(double), Offset = 10, Length = 8)] + [SocketDataPropertyConverter(Type = typeof(double), Offset = 10, Length = 8)] public double Value4 { get; set; } - [SocketDataProperty(Type = typeof(float), Offset = 18, Length = 4)] + [SocketDataPropertyConverter(Type = typeof(float), Offset = 18, Length = 4)] public float Value5 { get; set; } - [SocketDataProperty(Type = typeof(short), Offset = 22, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(short), Offset = 22, Length = 1)] public short Value6 { get; set; } - [SocketDataProperty(Type = typeof(ushort), Offset = 23, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(ushort), Offset = 23, Length = 1)] public ushort Value7 { get; set; } - [SocketDataProperty(Type = typeof(uint), Offset = 24, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(uint), Offset = 24, Length = 1)] public uint Value8 { get; set; } - [SocketDataProperty(Type = typeof(ulong), Offset = 25, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(ulong), Offset = 25, Length = 1)] public ulong Value9 { get; set; } - [SocketDataProperty(Type = typeof(bool), Offset = 26, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(bool), Offset = 26, Length = 1)] public bool Value10 { get; set; } - [SocketDataProperty(Type = typeof(EnumEducation), Offset = 27, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(EnumEducation), Offset = 27, Length = 1)] public EnumEducation Value11 { get; set; } - [SocketDataProperty(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] + [SocketDataPropertyConverter(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] public Foo? Value12 { get; set; } - [SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1)] + [SocketDataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1)] public string? Value14 { get; set; } public string? Value13 { get; set; } } - class MockSocketDataConverter: SocketDataConverter + class MockSocketDataConverter : SocketDataConverter { protected override bool Parse(ReadOnlyMemory data, MockEntity entity) { @@ -1214,4 +1274,11 @@ class NoConvertEntity public byte[]? Body { get; set; } } + + class OptionConvertEntity + { + public byte[]? Header { get; set; } + + public byte[]? Body { get; set; } + } }