Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,22 @@ private async Task CreateClient()
<p>数据适配器设计思路如下</p>

<ol class="ul-demo">
<li>使用 <code>SocketDataConverterAttribute</code> 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code>ISocketDataConverter</code>
<li>使用 <code>SocketDataTypeConverterAttribute</code> 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code>ISocketDataConverter</code>
接口
</li>
<li>使用 <code>SocketDataPropertyAttribute</code> 标签约定如何转换数据类型 (Property) 属性值</li>
<li>使用 <code>SocketDataPropertyConverterAttribute</code> 标签约定如何转换数据类型 (Property) 属性值</li>
</ol>

<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
<Pre>[SocketDataTypeConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
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; }
}</Pre>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,30 @@
<DemoBlock Title="@Localizer["NormalTitle"]"
Introduction="@Localizer["NormalIntro"]"
Name="Normal" ShowCode="false">
<p>通过 <code>SocketDataConverterAttribute</code> 类标签与 <code>SocketDataPropertyAttribute</code>
<p>
通过 <code>SocketDataTypeConverterAttribute</code> 类标签与 <code>SocketDataPropertyConverterAttribute</code>
属性标签可以将通讯数据自动转化为我们系统中需要的业务实体类,示例如下:</p>
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
<Pre>[SocketDataTypeConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
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; }
}</Pre>
<p class="code-label">1. <code>SocketDataConverter</code> 参数说明:</p>
<p class="code-label">1. <code>SocketDataTypeConverter</code> 参数说明:</p>
<ul class="ul-demo">
<li><b>Type</b>: 自定义转换器类型,组件库内置了 <code>SocketDataConverter</code> 泛型类,建议看一下源码非常方便扩展出自己的转换器
</li>
</ul>
<Pre>class MockEntitySocketDataConverter : SocketDataConverterBase&lt;MockEntity&gt;
{
public override bool TryConvertTo(ReadOnlyMemory&lt;byte&gt; data, [NotNullWhen(true)] out MockEntity? entity)
{
var v = new MockEntity();

// 调用基类的 Parse 方法即可
var ret = Parse(data, v);
entity = ret ? v : null;
return ret;
}
}</Pre>
<p><code>Parse</code> 方法也是可以重载自定义实现的,内置实现逻辑如下:</p>
<Pre>protected virtual bool Parse(ReadOnlyMemory&lt;byte&gt; 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&lt;SocketDataPropertyAttribute&gt;(false);
if (attr != null)
{
p.SetValue(entity, attr.ConvertTo(data));
}
}
ret = true;
}
return ret;
}</Pre>
<p>即遍历所有有 <code>SocketDataPropertyAttribute</code> 标签的属性对其进行过自动赋值</p>
<p class="code-label">2. <code>SocketDataPropertyAttribute</code> 参数说明</p>
<p class="code-label">2. <code>SocketDataPropertyConverterAttribute</code> 参数说明</p>
<ul class="ul-demo">
<li><b>Type</b>: 转换目标数据类型</li>
<li><b>Offset</b>: 数据偏移量,即在接收到的数据中起始位置</li>
Expand Down Expand Up @@ -95,16 +63,16 @@ class MockEntity
<li><code>SocketDataUInt64LittleEndianConverter</code> 转成 ulong 无符号整形小端读取</li>
</ul>
<p>自定义数据类型转化器示例</p>
<Pre>[SocketDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
<Pre>[SocketTypeDataConverter(Type = typeof(SocketDataConverter&lt;MockEntity&gt;))]
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; }
}</Pre>
<Pre>class FooConverter(string name) : ISocketDataPropertyConverter
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 the <code>SocketDataConverterAttribute</code> tag"
"NormalIntro": "Enable automatic data conversion through the <code>SocketDataTypeConverterAttribute</code> attribute"
},
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
"NetworkMonitorTitle": "NetworkMonitor",
Expand Down
2 changes: 1 addition & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -7129,7 +7129,7 @@
"DataEntityTitle": "Socket 数据转化实体类",
"DataEntityDescription": "接收到通讯数据后自动转成业务需要的实体类",
"NormalTitle": "基本用法",
"NormalIntro": "通过 <code>SocketDataConverterAttribute</code> 标签开启数据自动转换功能"
"NormalIntro": "通过 <code>SocketDataTypeConverterAttribute</code> 标签开启数据自动转换功能"
},
"BootstrapBlazor.Server.Components.Samples.NetworkMonitors": {
"NetworkMonitorTitle": "NetworkMonitor 网络状态",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv

services.AddTabItemBindOptions();
services.AddIconTheme();
services.AddSocketDataConverters();
return services;
}

Expand Down Expand Up @@ -212,7 +213,30 @@ static IServiceCollection AddTabItemBindOptions(this IServiceCollection services
}

/// <summary>
/// 增加第三方菜单路由与 Tab 捆绑字典配置
/// 增加 Socket 数据转换器集合配置项服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
static IServiceCollection AddSocketDataConverters(this IServiceCollection services)
{
services.AddOptionsMonitor<SocketDataConverterCollections>();
return services;
}

/// <summary>
/// 配置第三方数据模型与 <see cref="SocketDataConverterCollections"/> 数据转换器集合配置扩展方法
/// </summary>
/// <param name="services"></param>
/// <param name="configureOptions"></param>
/// <returns></returns>
public static IServiceCollection ConfigureSocketDataConverters(this IServiceCollection services, Action<SocketDataConverterCollections> configureOptions)
{
services.Configure(configureOptions);
return services;
}

/// <summary>
/// 配置第三方菜单路由与 Tab 标签页捆绑字典扩展方法
/// </summary>
/// <param name="services"></param>
/// <param name="configureOptions"></param>
Expand All @@ -236,7 +260,7 @@ static IServiceCollection AddIconTheme(this IServiceCollection services)
}

/// <summary>
/// IconThemeOptions 扩展配置方法
/// 配置 <see cref="IconThemeOptions"/> 扩展方法
/// </summary>
/// <param name="services"></param>
/// <param name="configureOptions"></param>
Expand Down
58 changes: 46 additions & 12 deletions src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text;
Expand Down Expand Up @@ -115,7 +116,7 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
/// </summary>
/// <remarks>This method sets up the <paramref name="client"/> to use the specified <paramref
/// name="adapter"/> for handling incoming data. If the <typeparamref name="TEntity"/> type is decorated with a <see
/// cref="SocketDataConverterAttribute"/>, 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 <paramref name="callback"/>. The callback is called with the converted entity or <see langword="null"/> if
/// conversion fails.</remarks>
/// <typeparam name="TEntity">The type of entity that the data package adapter will handle.</typeparam>
Expand All @@ -131,27 +132,60 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
await adapter.HandlerAsync(buffer);
};

ISocketDataConverter<TEntity>? converter = null;

var type = typeof(TEntity);
var converterType = type.GetCustomAttribute<SocketDataConverterAttribute>();
var converterType = type.GetCustomAttribute<SocketDataTypeConverterAttribute>();
if (converterType is { Type: not null })
{
// 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器
if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter<TEntity> 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<TEntity>();
}

if (converter == null)
{
// 设置正常回调
adapter.ReceivedCallBack = async buffer => await callback(default);
}
else
{
// 设置转化器
adapter.SetDataAdapterCallback(converter, callback);
}
}

private static void SetDataAdapterCallback<TEntity>(this IDataPackageAdapter adapter, ISocketDataConverter<TEntity> converter, Func<TEntity?, Task> callback)
{
adapter.ReceivedCallBack = async buffer =>
{
TEntity? ret = default;
if (converter.TryConvertTo(buffer, out var t))
{
ret = t;
}
await callback(ret);
};
}

private static ISocketDataConverter<TEntity>? GetSocketDataConverter<TEntity>(this ITcpSocketClient client)
{
ISocketDataConverter<TEntity>? converter = null;
if (client is IServiceProvider provider)
{
var converters = provider.GetRequiredService<IOptions<SocketDataConverterCollections>>().Value;
if (converters.TryGetTypeConverter<TEntity>(out var v))
{
converter = v;
}
}
return converter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@

namespace BootstrapBlazor.Components;

/// <summary>
/// Socket 数据转换器接口
/// </summary>
public interface ISocketDataConverter
{

}

/// <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>
public interface ISocketDataConverter<TEntity> : ISocketDataConverter
{
/// <summary>
/// Attempts to convert the specified data to an instance of <typeparamref name="TEntity"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ namespace BootstrapBlazor.Components;
/// 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 class SocketDataConverter<TEntity> : ISocketDataConverter<TEntity>
public class SocketDataConverter<TEntity>(SocketDataConverterCollections converters) : ISocketDataConverter<TEntity>
{
/// <summary>
/// 构造函数
/// </summary>
public SocketDataConverter() : this(new())
{

}

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -44,10 +52,14 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
var ret = false;
if (entity != null)
{
var unuseProperties = new List<PropertyInfo>(32);

// 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
foreach (var p in properties)
{
var attr = p.GetCustomAttribute<SocketDataPropertyAttribute>(false);
var attr = p.GetCustomAttribute<SocketDataPropertyConverterAttribute>(false)
?? GetPropertyConverterAttribute(p);
if (attr != null)
{
p.SetValue(entity, attr.ConvertTo(data));
Expand All @@ -57,4 +69,14 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
}
return ret;
}

private SocketDataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo)
{
SocketDataPropertyConverterAttribute? attr = null;
if (converters.TryGetPropertyConverter<TEntity>(propertyInfo, out var v))
{
attr = v;
}
return attr;
}
}
Loading
Loading