From 5b8e0ab385dd50ef6cb3395513b0f17f0ef64356 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 3 Sep 2025 14:04:17 +0800 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=20Socket=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=B7=A5=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BootstrapBlazor.Extensions.slnx | 107 ++ .../BootstrapBlazor.Socket.csproj | 42 - .../DataAdapter/DataPackageAdapter.cs | 73 - .../DataAdapter/IDataPackageAdapter.cs | 49 - .../DataConverter/BinConverter.cs | 57 - .../DataConverter/DataConverter.cs | 106 -- .../DataConverter/DataConverterCollections.cs | 101 -- .../DataPropertyConverterAttribute.cs | 45 - .../DataTypeConverterAttribute.cs | 17 - .../DataConverter/HexConverter.cs | 73 - .../DataConverter/IDataConverter.cs | 31 - .../DataHandler/DataPackageHandlerBase.cs | 68 - .../DelimiterDataPackageHandler.cs | 85 - .../FixLengthDataPackageHandler.cs | 53 - .../DataHandler/IDataPackageHandler.cs | 32 - .../Extensions/ActivatorExtensions.cs | 53 - .../Extensions/DataPropertyExtensions.cs | 105 -- .../Logging/SocketLogging.cs | 56 - .../PropertyConverter/DataBoolConverter.cs | 25 - .../DataByteArrayConverter.cs | 20 - .../PropertyConverter/DataByteConverter.cs | 20 - .../DataDoubleBigEndianConverter.cs | 32 - .../DataDoubleLittleEndianConverter.cs | 32 - .../PropertyConverter/DataEnumConverter.cs | 32 - .../DataInt16BigEndianConverter.cs | 32 - .../DataInt16LittleEndianConverter.cs | 32 - .../DataInt32BigEndianConverter.cs | 33 - .../DataInt32LittleEndianConverter.cs | 33 - .../DataInt64BigEndianConverter.cs | 33 - .../DataInt64LittleEndianConverter.cs | 33 - .../DataSingleBigEndianConverter.cs | 32 - .../DataSingleLittleEndianConverter.cs | 32 - .../PropertyConverter/DataStringConverter.cs | 23 - .../DataUInt16BigEndianConverter.cs | 32 - .../DataUInt16LittleEndianConverter.cs | 32 - .../DataUInt32BigEndianConverter.cs | 32 - .../DataUInt32LittleEndianConverter.cs | 32 - .../DataUInt64BigEndianConverter.cs | 32 - .../DataUInt64LittleEndianConverter.cs | 32 - .../IDataPropertyConverter.cs | 18 - .../Utility/ModbusCrc16.cs | 109 -- .../BootstrapBlazor.TcpSocket.csproj | 47 - .../DefaultTcpSocketClient.cs | 440 ----- .../DefaultTcpSocketClientProvider.cs | 92 - .../DefaultTcpSocketFactory.cs | 71 - .../Extensions/ITcpSocketClientExtensions.cs | 277 --- .../Extensions/TcpSocketExtensions.cs | 44 - .../Extensions/TcpSocketUtility.cs | 73 - .../ITcpSocketClient.cs | 92 - .../ITcpSocketClientProvider.cs | 72 - .../ITcpSocketFactory.cs | 29 - .../TcpSocketClientOptions.cs | 67 - .../ActivationExtensionsTest.cs | 45 - test/UnitTestTcpSocket/Assembly.cs | 5 - test/UnitTestTcpSocket/BinConverterTest.cs | 55 - .../DefaultSocketClientProviderTest.cs | 108 -- test/UnitTestTcpSocket/Foo.cs | 94 -- test/UnitTestTcpSocket/HexConverterTest.cs | 58 - test/UnitTestTcpSocket/ModbusCrcTest.cs | 36 - .../SocketDataConverterCollectionsTest.cs | 102 -- test/UnitTestTcpSocket/SocketLoggingTest.cs | 19 - .../UnitTestTcpSocket/TcpSocketFactoryTest.cs | 1488 ----------------- .../TcpSocketPropertyConverterTest.cs | 83 - test/UnitTestTcpSocket/TcpSocketUtiityTest.cs | 32 - .../UnitTestTcpSocket.csproj | 30 - .../TcpSocketFactoryTest.cs | 695 -------- .../UnitTestTouchSocket.csproj | 13 - 67 files changed, 107 insertions(+), 5976 deletions(-) create mode 100644 BootstrapBlazor.Extensions.slnx delete mode 100644 src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/BinConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/HexConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/Extensions/ActivatorExtensions.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/Logging/SocketLogging.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs delete mode 100644 src/extensions/BootstrapBlazor.Socket/Utility/ModbusCrc16.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs delete mode 100644 src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs delete mode 100644 test/UnitTestTcpSocket/ActivationExtensionsTest.cs delete mode 100644 test/UnitTestTcpSocket/Assembly.cs delete mode 100644 test/UnitTestTcpSocket/BinConverterTest.cs delete mode 100644 test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs delete mode 100644 test/UnitTestTcpSocket/Foo.cs delete mode 100644 test/UnitTestTcpSocket/HexConverterTest.cs delete mode 100644 test/UnitTestTcpSocket/ModbusCrcTest.cs delete mode 100644 test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs delete mode 100644 test/UnitTestTcpSocket/SocketLoggingTest.cs delete mode 100644 test/UnitTestTcpSocket/TcpSocketFactoryTest.cs delete mode 100644 test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs delete mode 100644 test/UnitTestTcpSocket/TcpSocketUtiityTest.cs delete mode 100644 test/UnitTestTcpSocket/UnitTestTcpSocket.csproj delete mode 100644 test/UnitTestTouchSocket/TcpSocketFactoryTest.cs delete mode 100644 test/UnitTestTouchSocket/UnitTestTouchSocket.csproj diff --git a/BootstrapBlazor.Extensions.slnx b/BootstrapBlazor.Extensions.slnx new file mode 100644 index 00000000..eb64485c --- /dev/null +++ b/BootstrapBlazor.Extensions.slnx @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj b/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj deleted file mode 100644 index 0cee55f8..00000000 --- a/src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - 9.0.14 - - - - BootstrapBlazor Socket - BootstrapBlazor extensions of Socket - - - - 8.0.* - 9.0.* - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs b/src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs deleted file mode 100644 index b84d9d55..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataAdapter/DataPackageAdapter.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataAdapters; - -/// -/// Provides a base implementation for adapting data packages between different systems or formats. -/// -/// 实例 -/// This abstract class serves as a foundation for implementing custom data package adapters. It defines -/// common methods for sending, receiving, and handling data packages, as well as a property for accessing the -/// associated data package handler. Derived classes should override the virtual methods to provide specific behavior -/// for handling data packages. -public class DataPackageAdapter(IDataPackageHandler? DataPackageHandler = null) : IDataPackageAdapter -{ - /// - /// - /// - public Func, ValueTask>? ReceivedCallBack { get; set; } - - /// - /// - /// - /// - /// - /// - public virtual async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) - { - if (DataPackageHandler != null) - { - DataPackageHandler.ReceivedCallBack ??= OnHandlerReceivedCallBack; - - // 如果存在数据处理器则调用其处理方法 - await DataPackageHandler.HandlerAsync(data, token); - } - } - - /// - /// - /// - /// - /// - /// - /// - public virtual bool TryConvertTo(ReadOnlyMemory data, IDataConverter socketDataConverter, out TEntity? entity) - { - entity = default; - var ret = socketDataConverter.TryConvertTo(data, out var v); - if (ret) - { - entity = v; - } - return ret; - } - - /// - /// Handles incoming data by invoking a callback method, if one is defined. - /// - /// This method is designed to be overridden in derived classes to provide custom handling of - /// incoming data. If a callback method is assigned, it will be invoked asynchronously with the provided - /// data. - /// The incoming data to be processed, represented as a read-only memory block of bytes. - /// - protected virtual async ValueTask OnHandlerReceivedCallBack(ReadOnlyMemory data) - { - if (ReceivedCallBack != null) - { - // 调用接收回调方法处理数据 - await ReceivedCallBack(data); - } - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs b/src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs deleted file mode 100644 index 382d8039..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataAdapter/IDataPackageAdapter.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataAdapters; - -/// -/// Defines an adapter for handling and transmitting data packages to a target destination. -/// -/// This interface provides methods for sending data asynchronously and configuring a data handler. -/// Implementations of this interface are responsible for managing the interaction between the caller and the underlying -/// data transmission mechanism. -public interface IDataPackageAdapter -{ - /// - /// Gets or sets the callback function to be invoked when data is received. - /// - /// The callback function is expected to handle the received data asynchronously. Ensure that the - /// implementation of the callback does not block the calling thread and completes promptly to avoid performance - /// issues. - Func, ValueTask>? ReceivedCallBack { get; set; } - - /// - /// Asynchronously receives data from a source and processes it. - /// - /// This method does not return any result directly. It is intended for scenarios where data is received - /// and processed asynchronously. Ensure that the parameter contains valid data before calling - /// this method. - /// A read-only memory region containing the data to be received. The caller must ensure the memory is valid and - /// populated. - /// An optional cancellation token that can be used to cancel the operation. Defaults to if - /// not provided. - /// A representing the asynchronous operation. The task completes when the data has been - /// successfully received and processed. - ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); - - /// - /// Attempts to convert the specified byte data into an entity of type . - /// - /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to its default value. - /// The type of the entity to convert the data to. - /// The byte data to be converted. - /// The converter used to transform the byte data into an entity. - /// When this method returns, contains the converted entity if the conversion was successful; otherwise, the default - /// value for the type of the entity. - /// if the conversion was successful; otherwise, . - bool TryConvertTo(ReadOnlyMemory data, IDataConverter socketDataConverter, out TEntity? entity); -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/BinConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/BinConverter.cs deleted file mode 100644 index 0c8f3b7e..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/BinConverter.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// 二进制 与 Byte 数组转换方法 -/// -public static class BinConverter -{ - /// - /// 将 byte[] 转为 二进制字符串 - /// Converts a byte array to its hexadecimal string representation. - /// - /// The byte array to convert. - /// - /// A string containing the hexadecimal representation of the byte array. - public static string ToString(byte[]? bytes, string? separator = "-") - { - if (bytes == null || bytes.Length == 0) - { - return string.Empty; - } - - return string.Join(separator, bytes.Select(i => Convert.ToString(i, 2).PadLeft(8, '0'))); - } - - /// - /// 将字符串转换为字节数组 - /// - /// - /// - /// - /// - public static byte[] ToBytes(string str, string? separator = null, StringSplitOptions options = StringSplitOptions.None) - { - // 把 str 内的 separator 符号替换掉 - if (!string.IsNullOrEmpty(separator)) - { - str = string.Join("", str.Split(separator, options).Select(i => i.PadLeft(8, '0'))); - } - - // 把 Hex 形式的 str 转化为 byte[] - if (str.Length % 8 != 0) - { - throw new ArgumentException("The raw string cannot have an odd number of digits. 参数 str 位数不正确无法转化为 二进制字节数组", nameof(str)); - } - - var bytes = new byte[str.Length / 8]; - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = Convert.ToByte(str.Substring(i * 8, 8), 2); - } - return bytes; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs deleted file mode 100644 index 672efdfc..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Socket.Logging; -using System.Diagnostics; -using System.Reflection; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// 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 DataConverter(DataConverterCollections converters) : IDataConverter -{ - /// - /// 构造函数 - /// - public DataConverter() : this(new()) - { - } - - /// - /// - /// - /// - /// - /// - public virtual bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out TEntity? entity) - { - var ret = false; - entity = default; - try - { - var v = CreateEntity(); - if (Parse(data, v)) - { - entity = v; - ret = true; - } - } - catch (Exception ex) - { - SocketLogging.LogError(ex, $"DataConverter {nameof(TryConvertTo)} failed"); - } - return ret; - } - - /// - /// 创建实体实例方法 - /// - /// - protected virtual TEntity CreateEntity() => Activator.CreateInstance(); - - /// - /// 将字节数据转换为指定实体类型的实例。 - /// - /// - /// - protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) - { - // 使用 SocketDataPropertyAttribute 特性获取数据转换规则 - var ret = false; - if (entity != null) - { - // 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器 - var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList(); - if (Debugger.IsAttached) - { - SocketLogging.LogDebug($"Data: {BitConverter.ToString(data.ToArray())}"); - } - foreach (var p in properties) - { - var attr = p.GetCustomAttribute(false) ?? GetPropertyConverterAttribute(p); - if (attr is { Type: not null }) - { - var value = attr.ConvertTo(data); - var valueType = value?.GetType(); - if (p.PropertyType.IsAssignableFrom(valueType)) - { - p.SetValue(entity, value); - } - else - { - SocketLogging.LogInformation($"{nameof(Parse)} failed. Start: {attr.Offset}. Length: {attr.Length}. Can't convert value from {GetValueType(valueType)} to {p.Name}({p.PropertyType})"); - } - } - } - ret = true; - } - return ret; - } - - private static string GetValueType(Type? type) => type?.FullName ?? "NULL"; - - private DataPropertyConverterAttribute? GetPropertyConverterAttribute(PropertyInfo propertyInfo) - { - DataPropertyConverterAttribute? attr = null; - if (converters.TryGetPropertyConverter(propertyInfo, out var v)) - { - attr = v; - } - return attr; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs deleted file mode 100644 index d0fc4654..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverterCollections.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Collections.Concurrent; -using System.Linq.Expressions; -using System.Reflection; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// 数据转换器集合类 -/// -public sealed class DataConverterCollections -{ - readonly ConcurrentDictionary _converters = new(); - readonly ConcurrentDictionary _propertyConverters = new(); - - /// - /// 增加指定 数据类型转换器方法 - /// - /// - /// - public void AddTypeConverter(IDataConverter converter) - { - var type = typeof(TEntity); - _converters.AddOrUpdate(type, t => converter, (t, v) => converter); - } - - /// - /// 增加默认数据类型转换器方法 转换器使用 - /// - /// - public void AddTypeConverter() => AddTypeConverter(new DataConverter(this)); - - /// - /// 添加属性类型转化器方法 - /// - /// - /// - /// - public void AddPropertyConverter(Expression> propertyExpression, DataPropertyConverterAttribute 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 IDataConverter? converter) - { - converter = null; - var ret = false; - if (_converters.TryGetValue(typeof(TEntity), out var v) && v is IDataConverter c) - { - converter = c; - ret = true; - } - return ret; - } - - /// - /// 获得指定数据类型属性转换器方法 - /// - /// - public bool TryGetPropertyConverter(Expression> propertyExpression, [NotNullWhen(true)] out DataPropertyConverterAttribute? 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 DataPropertyConverterAttribute? converterAttribute) - { - converterAttribute = null; - var ret = false; - if (_propertyConverters.TryGetValue(memberInfo, out var v)) - { - converterAttribute = v; - ret = true; - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs deleted file mode 100644 index 4f714946..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataPropertyConverterAttribute.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Represents an attribute used to mark a field as a socket data field. -/// -/// 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 | AttributeTargets.Field)] -public class DataPropertyConverterAttribute : Attribute -{ - /// - /// 获得/设置 数据类型 - /// - public Type? Type { get; set; } - - /// - /// 获得/设置 数据偏移量 - /// - public int Offset { get; set; } - - /// - /// 获得/设置 数据长度 - /// - public int Length { get; set; } - - /// - /// 获得/设置 数据编码名称 - /// - public string? EncodingName { get; set; } - - /// - /// 获得/设置 数据转换器类型 - /// - public Type? ConverterType { get; set; } - - /// - /// 获得/设置 数据转换器构造函数参数 - /// - public object?[]? ConverterParameters { get; set; } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs deleted file mode 100644 index 37bc80bc..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/DataTypeConverterAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// -/// -[AttributeUsage(AttributeTargets.Class)] -public class DataTypeConverterAttribute : Attribute -{ - /// - /// Gets or sets the type of the . - /// - public Type? Type { get; set; } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/HexConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/HexConverter.cs deleted file mode 100644 index 3b74ef3f..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/HexConverter.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// 十六进制 与 Byte 数组转换方法 -/// -public static class HexConverter -{ - /// - /// 将 byte[] 转为 16 进制字符串 - /// Converts a byte array to its hexadecimal string representation. - /// - /// The byte array to convert. - /// - /// - /// A string containing the hexadecimal representation of the byte array. - public static string ToString(byte[]? bytes, string? separator = "-", bool upper = true) - { - if (bytes == null || bytes.Length == 0) - { - return string.Empty; - } - - if (separator == "-") - { - return BitConverter.ToString(bytes); - } - - return string.Join(separator, bytes.Select(i => upper ? i.ToString("X2") : i.ToString("x2"))); - } - - /// - /// 将 byte[] 转为 16 进制字符串 - /// Converts a byte array to its hexadecimal string representation. - /// - /// - /// - /// - /// - public static string ToString(ReadOnlySpan span, string? separator = "-", bool upper = true) => ToString(span.ToArray(), separator, upper); - - /// - /// 将字符串转换为字节数组 - /// - /// - /// - /// - /// - public static byte[] ToBytes(string str, string? separator = null, StringSplitOptions options = StringSplitOptions.None) - { - // 把 str 内的 delimiter 符号替换掉 - if (!string.IsNullOrEmpty(separator)) - { - str = string.Join("", str.Split(separator, options)); - } - - // 把 Hex 形式的 str 转化为 byte[] - if (str.Length % 2 != 0) - { - throw new ArgumentException("The raw string cannot have an odd number of digits. 参数 str 位数不正确无法转化为 16 进制字节数组", nameof(str)); - } - - var bytes = new byte[str.Length / 2]; - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16); - } - return bytes; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs b/src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs deleted file mode 100644 index c62d6590..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataConverter/IDataConverter.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换器接口 -/// -public interface IDataConverter -{ - -} - -/// -/// Defines a method to convert raw socket data into a specified entity type. -/// -/// The type of entity to convert the data into. -public interface IDataConverter : IDataConverter -{ - /// - /// Attempts to convert the specified data to an instance of . - /// - /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to . - /// The data to be converted, represented as a read-only memory block of bytes. - /// When this method returns, contains the converted if the conversion succeeded; - /// otherwise, . - /// if the conversion was successful; otherwise, . - bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out TEntity? entity); -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs deleted file mode 100644 index 17b6237f..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataHandler/DataPackageHandlerBase.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataHandlers; - -/// -/// Provides a base implementation for handling data packages in a communication system. -/// -/// This abstract class defines the core contract for receiving and sending data packages. Derived -/// classes should override and extend its functionality to implement specific data handling logic. The default -/// implementation simply returns the provided data. -public abstract class DataPackageHandlerBase : IDataPackageHandler -{ - private Memory _lastReceiveBuffer = Memory.Empty; - - /// - /// - /// - public Func, ValueTask>? ReceivedCallBack { get; set; } - - /// - /// - /// - /// - /// - /// - public abstract ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); - - /// - /// Handles the processing of a sticky package by adjusting the provided buffer and length. - /// - /// This method processes the portion of the buffer beyond the specified length and updates the - /// internal state accordingly. The caller must ensure that the contains sufficient data - /// for the specified . - /// The memory buffer containing the data to process. - /// The length of the valid data within the buffer. - protected void SlicePackage(ReadOnlyMemory buffer, int length) - { - _lastReceiveBuffer = buffer[length..].ToArray().AsMemory(); - } - - /// - /// Concatenates the provided buffer with any previously stored data and returns the combined result. - /// - /// This method combines the provided buffer with any data stored in the internal buffer. After - /// concatenation, the internal buffer is cleared. The returned memory block is allocated from a shared memory pool - /// and should be used promptly to avoid holding onto pooled resources. - /// The buffer to concatenate with the previously stored data. Must not be empty. - /// A instance containing the concatenated data. If no previously stored data exists, the - /// method returns the input . - protected ReadOnlyMemory ConcatBuffer(ReadOnlyMemory buffer) - { - if (_lastReceiveBuffer.IsEmpty) - { - return buffer; - } - - // 计算缓存区长度 - Memory merged = new byte[_lastReceiveBuffer.Length + buffer.Length]; - _lastReceiveBuffer.CopyTo(merged); - buffer.CopyTo(merged[_lastReceiveBuffer.Length..]); - - // Clear the sticky buffer - _lastReceiveBuffer = Memory.Empty; - return merged; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs deleted file mode 100644 index d988a189..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataHandler/DelimiterDataPackageHandler.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers; -using System.Text; - -namespace BootstrapBlazor.Socket.DataHandlers; - -/// -/// Handles data packages that are delimited by a specific sequence of bytes or characters. -/// -/// This class provides functionality for processing data packages that are separated by a defined -/// delimiter. The delimiter can be specified as a string with an optional encoding or as a byte array. -public class DelimiterDataPackageHandler : DataPackageHandlerBase -{ - private readonly ReadOnlyMemory _delimiter; - - /// - /// Initializes a new instance of the class with the specified delimiter - /// and optional encoding. - /// - /// The string delimiter used to separate data packages. This value cannot be null or empty. - /// The character encoding used to convert the delimiter to bytes. If null, is used as - /// the default. - /// Thrown if is null or empty. - public DelimiterDataPackageHandler(string delimiter, Encoding? encoding = null) - { - if (string.IsNullOrEmpty(delimiter)) - { - throw new ArgumentNullException(nameof(delimiter), "Delimiter cannot be null or empty."); - } - - encoding ??= Encoding.UTF8; - _delimiter = encoding.GetBytes(delimiter); - } - - /// - /// Initializes a new instance of the class with the specified delimiters. - /// - /// An array of bytes representing the delimiters used to parse data packages. Cannot be . - /// Thrown if is . - public DelimiterDataPackageHandler(byte[] delimiter) - { - _delimiter = delimiter ?? throw new ArgumentNullException(nameof(delimiter), "Delimiter cannot be null."); - } - - /// - /// - /// - /// - /// - /// - public override async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) - { - data = ConcatBuffer(data); - - while (data.Length > 0) - { - var index = data.Span.IndexOfAny(_delimiter.Span); - var segment = index == -1 ? data : data[..index]; - var length = segment.Length + _delimiter.Length; - using var buffer = MemoryPool.Shared.Rent(length); - segment.CopyTo(buffer.Memory); - - if (index != -1) - { - SlicePackage(data, index + _delimiter.Length); - - _delimiter.CopyTo(buffer.Memory[index..]); - if (ReceivedCallBack != null) - { - await ReceivedCallBack(buffer.Memory[..length].ToArray()); - } - - data = data[(index + _delimiter.Length)..]; - } - else - { - SlicePackage(data, 0); - break; - } - } - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs deleted file mode 100644 index d71ba95a..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataHandler/FixLengthDataPackageHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataHandlers; - -/// -/// Handles fixed-length data packages by processing incoming data of a specified length. -/// -/// This class is designed to handle data packages with a fixed length, as specified during -/// initialization. It extends and overrides its behavior to process fixed-length -/// data. -/// The data package total data length. -public class FixLengthDataPackageHandler(int length) : DataPackageHandlerBase -{ - private readonly Memory _data = new byte[length]; - - private int _receivedLength; - - /// - /// - /// - /// - /// - /// - public override async ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default) - { - while (data.Length > 0) - { - // 拷贝数据 - var len = length - _receivedLength; - var segment = data.Length > len ? data[..len] : data; - segment.CopyTo(_data[_receivedLength..]); - - // 更新数据 - data = data[segment.Length..]; - - // 更新已接收长度 - _receivedLength += segment.Length; - - // 如果已接收长度等于总长度则触发回调 - if (_receivedLength == length) - { - // 重置已接收长度 - _receivedLength = 0; - if (ReceivedCallBack != null) - { - await ReceivedCallBack(_data); - } - } - } - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs b/src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs deleted file mode 100644 index 58627a31..00000000 --- a/src/extensions/BootstrapBlazor.Socket/DataHandler/IDataPackageHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataHandlers; - -/// -/// Defines an interface for adapting data packages to and from a TCP socket connection. -/// -/// Implementations of this interface are responsible for converting raw data received from a TCP socket -/// into structured data packages and vice versa. This allows for custom serialization and deserialization logic -/// tailored to specific application protocols. -public interface IDataPackageHandler -{ - /// - /// Gets or sets the callback function to be invoked when data is received asynchronously. - /// - Func, ValueTask>? ReceivedCallBack { get; set; } - - /// - /// Asynchronously receives data and processes it. - /// - /// The method is designed for asynchronous operations and may be used in scenarios where - /// efficient handling of data streams is required. Ensure that the parameter contains valid - /// data for processing, and handle potential cancellation using the . - /// The data to be received, represented as a read-only memory block of bytes. - /// A cancellation token that can be used to cancel the operation. Defaults to if not - /// provided. - /// A containing if the data was successfully received and - /// processed; otherwise, . - ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); -} diff --git a/src/extensions/BootstrapBlazor.Socket/Extensions/ActivatorExtensions.cs b/src/extensions/BootstrapBlazor.Socket/Extensions/ActivatorExtensions.cs deleted file mode 100644 index 371e03f5..00000000 --- a/src/extensions/BootstrapBlazor.Socket/Extensions/ActivatorExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Socket.Logging; -using System.Reflection; - -namespace System; - -/// -/// Activator 扩展方法 -/// -public static class ActivatorExtensions -{ - /// - /// 通过指定类型与参数创建实例方法 - /// - /// - /// - /// - public static object? CreateInstance(this Type type, object?[]? args = null) - { - var bindings = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; - - object? instance = null; - try - { - instance = Activator.CreateInstance(type, bindings, null, args, null); - } - catch (Exception ex) - { - SocketLogging.LogError(ex, $"Create Instance {type.FullName} failed"); - } - return instance; - } - - /// - /// 通过指定类型与参数创建实例方法 - /// - /// - /// - /// - public static TType? CreateInstance(this Type type, object?[]? args = null) - { - TType? ret = default; - var value = type.CreateInstance(args); - if (value is TType v) - { - ret = v; - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs b/src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs deleted file mode 100644 index 8f117a73..00000000 --- a/src/extensions/BootstrapBlazor.Socket/Extensions/DataPropertyExtensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -static class DataPropertyExtensions -{ - public static IDataPropertyConverter? GetConverter(this DataPropertyConverterAttribute attribute) - { - return attribute.GetConverterByType() ?? attribute.GetDefaultConverter(); - } - - private static IDataPropertyConverter? GetConverterByType(this DataPropertyConverterAttribute attribute) - { - IDataPropertyConverter? converter = null; - var converterType = attribute.ConverterType; - if (converterType != null) - { - var converterParameters = attribute.ConverterParameters; - converter = converterType.CreateInstance(converterParameters); - } - return converter; - } - - private static IDataPropertyConverter? GetDefaultConverter(this DataPropertyConverterAttribute attribute) - { - IDataPropertyConverter? converter = null; - var type = attribute.Type; - if (type != null) - { - if (type == typeof(byte)) - { - converter = new DataByteConverter(); - } - else if (type == typeof(byte[])) - { - converter = new DataByteArrayConverter(); - } - else if (type == typeof(string)) - { - converter = new DataStringConverter(attribute.EncodingName); - } - else if (type.IsEnum) - { - converter = new DataEnumConverter(attribute.Type); - } - else if (type == typeof(bool)) - { - converter = new DataBoolConverter(); - } - else if (type == typeof(short)) - { - converter = new DataInt16BigEndianConverter(); - } - else if (type == typeof(int)) - { - converter = new DataInt32BigEndianConverter(); - } - else if (type == typeof(long)) - { - converter = new DataInt64BigEndianConverter(); - } - else if (type == typeof(float)) - { - converter = new DataSingleBigEndianConverter(); - } - else if (type == typeof(double)) - { - converter = new DataDoubleBigEndianConverter(); - } - else if (type == typeof(ushort)) - { - converter = new DataUInt16BigEndianConverter(); - } - else if (type == typeof(uint)) - { - converter = new DataUInt32BigEndianConverter(); - } - else if (type == typeof(ulong)) - { - converter = new DataUInt64BigEndianConverter(); - } - } - return converter; - } - - public static object? ConvertTo(this DataPropertyConverterAttribute attribute, ReadOnlyMemory data) - { - object? ret = null; - var start = attribute.Offset; - var length = attribute.Length; - - if (data.Length >= start + length) - { - var buffer = data.Slice(start, length); - var converter = attribute.GetConverter(); - if (converter != null) - { - ret = converter.Convert(buffer); - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/Logging/SocketLogging.cs b/src/extensions/BootstrapBlazor.Socket/Logging/SocketLogging.cs deleted file mode 100644 index 7f7a2ce1..00000000 --- a/src/extensions/BootstrapBlazor.Socket/Logging/SocketLogging.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using Microsoft.Extensions.Logging; - -namespace BootstrapBlazor.Socket.Logging; - -/// -/// Socket 日志记录类 -/// -public static class SocketLogging -{ - private static ILogger? _logger; - private static bool _inited; - - /// - /// 返回 是否已经初始化 - /// - public static bool Inited => _inited; - - /// - /// 初始化 ILogger 实例 - /// - /// - public static void Init(ILogger logger) - { - _inited = true; - _logger = logger; - } - - /// - /// 记录异常信息方法 - /// - /// - /// - public static void LogError(Exception ex, string? message = null) => _logger?.LogError(ex, "{message}", message); - - /// - /// 记录警告信息方法 - /// - /// - public static void LogWarning(string message) => _logger?.LogWarning("{message}", message); - - /// - /// 记录信息方法 - /// - /// - public static void LogInformation(string message) => _logger?.LogInformation("{message}", message); - - /// - /// 记录调试信息方法 - /// - /// - public static void LogDebug(string message) => _logger?.LogDebug("{message}", message); -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs deleted file mode 100644 index c2593e28..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataBoolConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 bool 数据转换器 -/// -public class DataBoolConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - var ret = false; - if (data.Length > 0) - { - ret = data.Span[0] != 0x00; - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs deleted file mode 100644 index f3b0c18e..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteArrayConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 byte[] 数组转换器 -/// -public class DataByteArrayConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - return data.ToArray(); - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteConverter.cs deleted file mode 100644 index c16c9566..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataByteConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 byte 转换器 -/// -public class DataByteConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - return data.Length > 0 ? data.Span[0] : byte.MinValue; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs deleted file mode 100644 index daed3af2..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleBigEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 double 数据大端转换器 -/// -public class DataDoubleBigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - double ret = 0; - if (data.Length <= 8) - { - Span paddedSpan = stackalloc byte[8]; - data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); - if (BinaryPrimitives.TryReadDoubleBigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs deleted file mode 100644 index 63d25727..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataDoubleLittleEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 double 数据小端转换器 -/// -public class DataDoubleLittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - double ret = 0; - if (data.Length <= 8) - { - Span paddedSpan = stackalloc byte[8]; - data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); - if (BinaryPrimitives.TryReadDoubleLittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs deleted file mode 100644 index f636e19f..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataEnumConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 Enum 数据转换器 -/// -public class DataEnumConverter(Type? type) : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - object? ret = null; - if (type != null) - { - if (data.Length == 1) - { - var v = data.Span[0]; - if (Enum.TryParse(type, v.ToString(), out var enumValue)) - { - ret = enumValue; - } - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs deleted file mode 100644 index bbc76ef4..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16BigEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 short 数据大端转换器 -/// -public class DataInt16BigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - short ret = 0; - if (data.Length <= 2) - { - Span paddedSpan = stackalloc byte[2]; - data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); - if (BinaryPrimitives.TryReadInt16BigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs deleted file mode 100644 index 3452ed51..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt16LittleEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 short 数据小端转换器 -/// -public class DataInt16LittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - short ret = 0; - if (data.Length <= 2) - { - Span paddedSpan = stackalloc byte[2]; - data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); - if (BinaryPrimitives.TryReadInt16LittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs deleted file mode 100644 index 445b6f59..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32BigEndianConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 int 数据大端转换器 -/// -public class DataInt32BigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - var ret = 0; - if (data.Length <= 4) - { - Span paddedSpan = stackalloc byte[4]; - data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); - - if (BinaryPrimitives.TryReadInt32BigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs deleted file mode 100644 index f2744e07..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt32LittleEndianConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 int 数据小端转换器 -/// -public class DataInt32LittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - var ret = 0; - if (data.Length <= 4) - { - Span paddedSpan = stackalloc byte[4]; - data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); - - if (BinaryPrimitives.TryReadInt32LittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs deleted file mode 100644 index b82d5044..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64BigEndianConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 long 数据大端转换器 -/// -public class DataInt64BigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - long ret = 0; - if (data.Length <= 8) - { - Span paddedSpan = stackalloc byte[8]; - data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); - - if (BinaryPrimitives.TryReadInt64BigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs deleted file mode 100644 index d78a104c..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataInt64LittleEndianConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 long 数据小端转换器 -/// -public class DataInt64LittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - long ret = 0; - if (data.Length <= 8) - { - Span paddedSpan = stackalloc byte[8]; - data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); - - if (BinaryPrimitives.TryReadInt64LittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs deleted file mode 100644 index a047a201..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleBigEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 float 数据大端转换器 -/// -public class DataSingleBigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - float ret = 0; - if (data.Length <= 4) - { - Span paddedSpan = stackalloc byte[4]; - data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); - if (BinaryPrimitives.TryReadSingleBigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs deleted file mode 100644 index c3387816..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataSingleLittleEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 float 数据小端转换器 -/// -public class DataSingleLittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - float ret = 0; - if (data.Length <= 4) - { - Span paddedSpan = stackalloc byte[4]; - data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); - if (BinaryPrimitives.TryReadSingleLittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs deleted file mode 100644 index 6515f823..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataStringConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Text; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 string 数据转换器 -/// -public class DataStringConverter(string? encodingName) : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - var encoding = string.IsNullOrEmpty(encodingName) ? Encoding.UTF8 : Encoding.GetEncoding(encodingName); - return encoding.GetString(data.Span); - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs deleted file mode 100644 index ec8b82be..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16BigEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 ushort 数据大端转换器 -/// -public class DataUInt16BigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - ushort ret = 0; - if (data.Length <= 2) - { - Span paddedSpan = stackalloc byte[2]; - data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); - if (BinaryPrimitives.TryReadUInt16BigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs deleted file mode 100644 index dae01236..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt16LittleEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 ushort 数据小端转换器 -/// -public class DataUInt16LittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - ushort ret = 0; - if (data.Length <= 2) - { - Span paddedSpan = stackalloc byte[2]; - data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); - if (BinaryPrimitives.TryReadUInt16LittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs deleted file mode 100644 index d3b1f0a1..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32BigEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 uint 数据大端转换器 -/// -public class DataUInt32BigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - uint ret = 0; - if (data.Length <= 4) - { - Span paddedSpan = stackalloc byte[4]; - data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); - if (BinaryPrimitives.TryReadUInt32BigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs deleted file mode 100644 index 482f8a8d..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt32LittleEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 uint 数据小端转换器 -/// -public class DataUInt32LittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - uint ret = 0; - if (data.Length <= 4) - { - Span paddedSpan = stackalloc byte[4]; - data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); - if (BinaryPrimitives.TryReadUInt32LittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs deleted file mode 100644 index 737812a8..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64BigEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 ulong 数据大端转换器 -/// -public class DataUInt64BigEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - ulong ret = 0; - if (data.Length <= 8) - { - Span paddedSpan = stackalloc byte[8]; - data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); - if (BinaryPrimitives.TryReadUInt64BigEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs deleted file mode 100644 index 51255bdc..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/DataUInt64LittleEndianConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Buffers.Binary; - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换为 ulong 数据小端转换器 -/// -public class DataUInt64LittleEndianConverter : IDataPropertyConverter -{ - /// - /// - /// - /// - public object? Convert(ReadOnlyMemory data) - { - ulong ret = 0; - if (data.Length <= 8) - { - Span paddedSpan = stackalloc byte[8]; - data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); - if (BinaryPrimitives.TryReadUInt64LittleEndian(paddedSpan, out var v)) - { - ret = v; - } - } - return ret; - } -} diff --git a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs b/src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs deleted file mode 100644 index c95be6ac..00000000 --- a/src/extensions/BootstrapBlazor.Socket/PropertyConverter/IDataPropertyConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.DataConverters; - -/// -/// Socket 数据转换器接口 -/// -public interface IDataPropertyConverter -{ - /// - /// 将数据转换为指定类型的对象 - /// - /// - /// - object? Convert(ReadOnlyMemory data); -} diff --git a/src/extensions/BootstrapBlazor.Socket/Utility/ModbusCrc16.cs b/src/extensions/BootstrapBlazor.Socket/Utility/ModbusCrc16.cs deleted file mode 100644 index 74190383..00000000 --- a/src/extensions/BootstrapBlazor.Socket/Utility/ModbusCrc16.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.Socket.Algorithm; - -/// -/// Modubs CRC-16 查表算法 -/// -public static class ModbusCrc16 -{ - // 预计算的CRC表(256个条目) - private static readonly ushort[] CrcTable = - [ - 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, - 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, - 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, - 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, - 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, - 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, - 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, - 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, - 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, - 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, - 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, - 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, - 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, - 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, - 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, - 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, - 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, - 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, - 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, - 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, - 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, - 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, - 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, - 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, - 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, - 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, - 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, - 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, - 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, - 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, - 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, - 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 - ]; - - /// - /// 计算 Modbus CRC-16 校验码 - /// - /// 要计算的数据字节数组 - /// CRC校验码(低字节在前) - public static ushort Compute(ReadOnlySpan data) - { - ushort crc = 0xFFFF; - foreach (var b in data) - { - crc = (ushort)((crc >> 8) ^ CrcTable[(crc ^ b) & 0xFF]); - } - return crc; - } - - /// - /// 计算 CRC 并将结果添加到消息末尾(Modbus格式) - /// - /// 原始数据 - /// 带 CRC 校验码的数据 - public static ReadOnlySpan Append(ReadOnlySpan data) - { - ushort crc = Compute(data); - - // 使用 stackalloc 避免堆分配(小数据时) - if (data.Length <= 256) - { - Span result = stackalloc byte[data.Length + 2]; - data.CopyTo(result); - result[data.Length] = (byte)(crc & 0xFF); - result[data.Length + 1] = (byte)(crc >> 8); - return result.ToArray(); - } - else - { - // 大数据使用常规数组 - byte[] result = new byte[data.Length + 2]; - data.CopyTo(result); - result[data.Length] = (byte)(crc & 0xFF); - result[data.Length + 1] = (byte)(crc >> 8); - return result; - } - } - - /// - /// 验证带 CRC 的数据是否有效 - /// - /// 包含 CRC 校验码的数据 - /// 验证结果 - public static bool Validate(ReadOnlySpan dataWithCrc) - { - if (dataWithCrc.Length < 2) - { - return false; - } - - ushort receivedCrc = (ushort)(dataWithCrc[^1] << 8 | dataWithCrc[^2]); - ushort calculatedCrc = Compute(dataWithCrc[..^2]); - return receivedCrc == calculatedCrc; - } -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj b/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj deleted file mode 100644 index 69c92032..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - 9.0.5 - - - - BootstrapBlazor Socket TcpClient - BootstrapBlazor extensions of TcpSocket - - - - 8.0.* - 9.0.* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs deleted file mode 100644 index 52486cc5..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClient.cs +++ /dev/null @@ -1,440 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Buffers; -using System.Net; -using System.Runtime.Versioning; - -namespace BootstrapBlazor.TcpSocket; - -[UnsupportedOSPlatform("browser")] -class DefaultTcpSocketClient(TcpSocketClientOptions options) : IServiceProvider, ITcpSocketClient -{ - /// - /// Gets or sets the socket client provider used for managing socket connections. - /// - private ITcpSocketClientProvider? SocketClientProvider { get; set; } - - /// - /// Gets or sets the logger instance used for logging messages and events. - /// - private ILogger? Logger { get; set; } - - /// - /// Gets or sets the service provider used to resolve dependencies. - /// - [NotNull] - public IServiceProvider? ServiceProvider { get; set; } - - /// - /// - /// - public TcpSocketClientOptions Options => options; - - /// - /// - /// - public bool IsConnected => SocketClientProvider?.IsConnected ?? false; - - /// - /// - /// - public IPEndPoint LocalEndPoint => SocketClientProvider?.LocalEndPoint ?? new IPEndPoint(IPAddress.Any, 0); - - /// - /// - /// - public Func, ValueTask>? ReceivedCallback { get; set; } - - /// - /// - /// - public Func? OnConnecting { get; set; } - - /// - /// - /// - public Func? OnConnected { get; set; } - - private IPEndPoint? _remoteEndPoint; - private IPEndPoint? _localEndPoint; - private CancellationTokenSource? _receiveCancellationTokenSource; - private CancellationTokenSource? _autoConnectTokenSource; - - private readonly SemaphoreSlim _semaphoreSlim = new(1, 1); - - /// - /// - /// - /// - /// - /// - public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - if (IsConnected) - { - return true; - } - - var connectionToken = GenerateConnectionToken(token); - try - { - await _semaphoreSlim.WaitAsync(connectionToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // 如果信号量等待被取消,则直接返回 IsConnected - // 不管是超时还是被取消,都不需要重连,肯定有其他线程在连接中 - return IsConnected; - } - - if (IsConnected) - { - _semaphoreSlim.Release(); - return true; - } - - var reconnect = true; - var ret = false; - SocketClientProvider = ServiceProvider?.GetRequiredService() - ?? throw new InvalidOperationException("SocketClientProvider is not registered in the service provider."); - - try - { - if (OnConnecting != null) - { - await OnConnecting(); - } - ret = await ConnectCoreAsync(SocketClientProvider, endPoint, connectionToken); - if (OnConnected != null) - { - await OnConnected(); - } - } - catch (OperationCanceledException ex) - { - if (token.IsCancellationRequested) - { - Log(LogLevel.Warning, ex, $"TCP Socket connect operation was canceled from {LocalEndPoint} to {endPoint}"); - reconnect = false; - } - else - { - Log(LogLevel.Warning, ex, $"TCP Socket connect operation timed out from {LocalEndPoint} to {endPoint}"); - } - } - catch (Exception ex) - { - Log(LogLevel.Error, ex, $"TCP Socket connection failed from {LocalEndPoint} to {endPoint}"); - } - - // 释放信号量 - _semaphoreSlim.Release(); - - if (reconnect) - { - _autoConnectTokenSource = new(); - - if (!ret) - { - Reconnect(); - } - } - return ret; - } - - private void Reconnect() - { - if (_autoConnectTokenSource != null && options.IsAutoReconnect && _remoteEndPoint != null) - { - Task.Run(async () => - { - try - { - await Task.Delay(options.ReconnectInterval, _autoConnectTokenSource.Token).ConfigureAwait(false); - await ConnectAsync(_remoteEndPoint, _autoConnectTokenSource.Token).ConfigureAwait(false); - } - catch { } - }, CancellationToken.None).ConfigureAwait(false); - } - } - - private async ValueTask ConnectCoreAsync(ITcpSocketClientProvider provider, IPEndPoint endPoint, CancellationToken token) - { - // 释放资源 - await CloseCoreAsync(); - - // 创建新的 TcpClient 实例 - provider.LocalEndPoint = Options.LocalEndPoint; - - _localEndPoint = Options.LocalEndPoint; - _remoteEndPoint = endPoint; - - var ret = await provider.ConnectAsync(endPoint, token); - - if (ret) - { - _localEndPoint = provider.LocalEndPoint; - - if (options.IsAutoReceive) - { - _ = Task.Run(AutoReceiveAsync, CancellationToken.None).ConfigureAwait(false); - } - } - return ret; - } - - private CancellationToken GenerateConnectionToken(CancellationToken token) - { - var connectionToken = token; - if (Options.ConnectTimeout > 0) - { - // 设置连接超时时间 - var connectTokenSource = new CancellationTokenSource(options.ConnectTimeout); - connectionToken = CancellationTokenSource.CreateLinkedTokenSource(token, connectTokenSource.Token).Token; - } - return connectionToken; - } - - /// - /// - /// - /// - /// - /// - public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - if (SocketClientProvider is not { IsConnected: true }) - { - throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}"); - } - - var ret = false; - var reconnect = true; - try - { - var sendToken = token; - if (options.SendTimeout > 0) - { - // 设置发送超时时间 - var sendTokenSource = new CancellationTokenSource(options.SendTimeout); - sendToken = CancellationTokenSource.CreateLinkedTokenSource(token, sendTokenSource.Token).Token; - } - ret = await SocketClientProvider.SendAsync(data, sendToken); - } - catch (OperationCanceledException ex) - { - if (token.IsCancellationRequested) - { - reconnect = false; - 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) - { - Log(LogLevel.Error, ex, $"TCP Socket send failed from {_localEndPoint} to {_remoteEndPoint}"); - } - - Log(LogLevel.Information, null, $"Sending data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {data.Length} Data Content: {BitConverter.ToString(data.ToArray())} Result: {ret}"); - - if (!ret && reconnect) - { - // 如果发送失败并且需要重连则尝试重连 - Reconnect(); - } - return ret; - } - - /// - /// - /// - /// - /// - public async ValueTask> ReceiveAsync(CancellationToken token = default) - { - if (SocketClientProvider is not { IsConnected: true }) - { - throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}"); - } - - if (options.IsAutoReceive) - { - throw new InvalidOperationException("Cannot call ReceiveAsync when IsAutoReceive is true. Use the auto-receive mechanism instead."); - } - - using var block = MemoryPool.Shared.Rent(options.ReceiveBufferSize); - var buffer = block.Memory; - var len = await ReceiveCoreAsync(SocketClientProvider, buffer, token); - if (len == 0) - { - Reconnect(); - } - return buffer[..len]; - } - - private async ValueTask AutoReceiveAsync() - { - // 自动接收方法 - _receiveCancellationTokenSource ??= new(); - while (_receiveCancellationTokenSource is { IsCancellationRequested: false }) - { - if (SocketClientProvider is not { IsConnected: true }) - { - throw new InvalidOperationException($"TCP Socket is not connected {LocalEndPoint}"); - } - - using var block = MemoryPool.Shared.Rent(options.ReceiveBufferSize); - var buffer = block.Memory; - var len = await ReceiveCoreAsync(SocketClientProvider, buffer, _receiveCancellationTokenSource.Token); - if (len == 0) - { - // 远端关闭或者 DisposeAsync 方法被调用时退出 - break; - } - } - - Reconnect(); - } - - private async ValueTask ReceiveCoreAsync(ITcpSocketClientProvider client, Memory buffer, CancellationToken token) - { - var reconnect = true; - var len = 0; - try - { - var receiveToken = token; - if (options.ReceiveTimeout > 0) - { - // 设置接收超时时间 - var receiveTokenSource = new CancellationTokenSource(options.ReceiveTimeout); - receiveToken = CancellationTokenSource.CreateLinkedTokenSource(receiveToken, receiveTokenSource.Token).Token; - } - - len = await client.ReceiveAsync(buffer, receiveToken); - if (len == 0) - { - // 远端主机关闭链路 - Log(LogLevel.Information, null, $"TCP Socket {_localEndPoint} received 0 data closed by {_remoteEndPoint}"); - buffer = Memory.Empty; - } - else - { - buffer = buffer[..len]; - } - - if (ReceivedCallback != null) - { - // 如果订阅回调则触发回调 - await ReceivedCallback(buffer); - } - } - catch (OperationCanceledException ex) - { - if (token.IsCancellationRequested) - { - Log(LogLevel.Warning, ex, $"TCP Socket receive operation canceled from {_localEndPoint} to {_remoteEndPoint}"); - reconnect = false; - } - else - { - Log(LogLevel.Warning, ex, $"TCP Socket receive operation timed out from {_localEndPoint} to {_remoteEndPoint}"); - } - } - catch (Exception ex) - { - Log(LogLevel.Error, ex, $"TCP Socket receive failed from {_localEndPoint} to {_remoteEndPoint}"); - } - - Log(LogLevel.Information, null, $"Receiving data from {_localEndPoint} to {_remoteEndPoint}, Data Length: {len} Data Content: {BitConverter.ToString(buffer.ToArray())}"); - - if (len == 0 && reconnect) - { - // 如果接收数据长度为 0 并且需要重连则尝试重连 - Reconnect(); - } - return len; - } - - /// - /// Logs a message with the specified log level, exception, and additional context. - /// - private void Log(LogLevel logLevel, Exception? ex, string? message) - { - if (options.EnableLog) - { - Logger ??= ServiceProvider?.GetRequiredService>(); - Logger?.Log(logLevel, ex, "{Message}", message); - } - } - - /// - /// - /// - public async ValueTask CloseAsync() - { - // 取消重连任务 - if (_autoConnectTokenSource != null) - { - _autoConnectTokenSource.Cancel(); - _autoConnectTokenSource.Dispose(); - _autoConnectTokenSource = null; - } - - await CloseCoreAsync(); - } - - private async ValueTask CloseCoreAsync() - { - // 取消接收数据的任务 - if (_receiveCancellationTokenSource != null) - { - _receiveCancellationTokenSource.Cancel(); - _receiveCancellationTokenSource.Dispose(); - _receiveCancellationTokenSource = null; - } - - if (SocketClientProvider != null) - { - await SocketClientProvider.CloseAsync(); - } - } - - /// - /// - /// - /// - /// - public object? GetService(Type serviceType) => ServiceProvider.GetService(serviceType); - - /// - /// Releases the resources used by the current instance of the class. - /// - /// This method is called to free both managed and unmanaged resources. If the parameter is , the method releases managed resources in addition to - /// unmanaged resources. Override this method in a derived class to provide custom cleanup logic. - /// to release both managed and unmanaged resources; to release only - /// unmanaged resources. - private async ValueTask DisposeAsync(bool disposing) - { - if (disposing) - { - await CloseAsync(); - } - } - - /// - /// - /// - public async ValueTask DisposeAsync() - { - await DisposeAsync(true); - GC.SuppressFinalize(this); - } -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs deleted file mode 100644 index 8fa3e886..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketClientProvider.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; -using System.Net.Sockets; -using System.Runtime.Versioning; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// TcpSocket 客户端默认实现 -/// -[UnsupportedOSPlatform("browser")] -class DefaultTcpSocketClientProvider : ITcpSocketClientProvider -{ - private TcpClient? _client; - - /// - /// - /// - public bool IsConnected => _client?.Connected ?? false; - - /// - /// - /// - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); - - /// - /// - /// - public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - _client = new TcpClient(LocalEndPoint); - await _client.ConnectAsync(endPoint, token).ConfigureAwait(false); - if (_client.Connected) - { - if (_client.Client.LocalEndPoint is IPEndPoint localEndPoint) - { - LocalEndPoint = localEndPoint; - } - } - return _client.Connected; - } - - /// - /// - /// - public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - var ret = false; - if (_client != null) - { - var stream = _client.GetStream(); - await stream.WriteAsync(data, token).ConfigureAwait(false); - ret = true; - } - return ret; - } - - /// - /// - /// - public async ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - var len = 0; - if (_client is { Connected: true }) - { - var stream = _client.GetStream(); - len = await stream.ReadAsync(buffer, token).ConfigureAwait(false); - - if (len == 0) - { - _client.Close(); - } - } - return len; - } - - /// - /// - /// - public ValueTask CloseAsync() - { - if (_client != null) - { - _client.Close(); - _client = null; - } - return ValueTask.CompletedTask; - } -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs b/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs deleted file mode 100644 index bd181254..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/DefaultTcpSocketFactory.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Socket.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Collections.Concurrent; -using System.Runtime.Versioning; - -namespace BootstrapBlazor.TcpSocket; - -[UnsupportedOSPlatform("browser")] -sealed class DefaultTcpSocketFactory(IServiceProvider provider) : ITcpSocketFactory -{ - private readonly ConcurrentDictionary _pool = new(); - - public ITcpSocketClient GetOrCreate(string name, Action valueFactory) - { - if (!SocketLogging.Inited) - { - var logger = provider.GetService>(); - if (logger != null) - { - SocketLogging.Init(logger); - } - } - return _pool.GetOrAdd(name, key => - { - var options = new TcpSocketClientOptions(); - valueFactory(options); - var client = new DefaultTcpSocketClient(options) - { - ServiceProvider = provider, - }; - return client; - }); - } - - public ITcpSocketClient? Remove(string name) - { - ITcpSocketClient? client = null; - if (_pool.TryRemove(name, out var c)) - { - client = c; - } - return client; - } - - private async ValueTask DisposeAsync(bool disposing) - { - if (disposing) - { - // 释放托管资源 - foreach (var socket in _pool.Values) - { - await socket.DisposeAsync(); - } - _pool.Clear(); - } - } - - /// - /// - /// - public async ValueTask DisposeAsync() - { - await DisposeAsync(true); - GC.SuppressFinalize(this); - } -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs deleted file mode 100644 index f229bd56..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System.Reflection; -using System.Runtime.Versioning; -using System.Text; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// 扩展方法类 -/// -[UnsupportedOSPlatform("browser")] -public static class ITcpSocketClientExtensions -{ - /// - /// Sends the specified string content to the connected TCP socket client asynchronously. - /// - /// This method converts the provided string content into a byte array using the specified - /// encoding (or UTF-8 by default) and sends it to the connected TCP socket client. Ensure the client is connected - /// before calling this method. - /// The TCP socket client to which the content will be sent. Cannot be . - /// The string content to send. Cannot be or empty. - /// The character encoding to use for converting the string content to bytes. If , UTF-8 - /// encoding is used by default. - /// A to observe while waiting for the operation to complete. - /// A that represents the asynchronous operation. The result is if the content was sent successfully; otherwise, . - public static ValueTask SendAsync(this ITcpSocketClient client, string content, Encoding? encoding = null, CancellationToken token = default) - { - var buffer = encoding?.GetBytes(content) ?? Encoding.UTF8.GetBytes(content); - return client.SendAsync(buffer, token); - } - - /// - /// Establishes an asynchronous connection to the specified host and port. - /// - /// The TCP socket client to which the content will be sent. Cannot be . - /// The hostname or IP address of the server to connect to. Cannot be null or empty. - /// The port number on the server to connect to. Must be a valid port number between 0 and 65535. - /// An optional to cancel the connection attempt. Defaults to if not provided. - /// A task that represents the asynchronous operation. The task result is if the connection - /// is successfully established; otherwise, . - public static ValueTask ConnectAsync(this ITcpSocketClient client, string ipString, int port, CancellationToken token = default) - { - var endPoint = TcpSocketUtility.ConvertToIpEndPoint(ipString, port); - return client.ConnectAsync(endPoint, token); - } - - private static readonly Dictionary, ValueTask> Callback)>> Cache = []; - - /// - /// 增加 数据适配器及其对应的回调方法 - /// - /// - /// - /// - public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func, ValueTask> callback) - { - async ValueTask ReceivedCallback(ReadOnlyMemory buffer) - { - // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 - await adapter.HandlerAsync(buffer); - } - - if (Cache.TryGetValue(client, out var list)) - { - list.Add((adapter, ReceivedCallback)); - } - else - { - Cache.Add(client, [(adapter, ReceivedCallback)]); - } - - client.ReceivedCallback += ReceivedCallback; - - // 设置 DataPackageAdapter 的回调函数 - adapter.ReceivedCallBack = callback; - } - - /// - /// 移除 数据适配器及其对应的回调方法 - /// - /// - /// - public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func, ValueTask> callback) - { - if (Cache.TryGetValue(client, out var list)) - { - var items = list.Where(i => i.Adapter.ReceivedCallBack == callback).ToList(); - foreach (var c in items) - { - client.ReceivedCallback -= c.Callback; - list.Remove(c); - } - } - } - - /// - /// 通过指定 数据处理实例,设置数据适配器并配置回调方法,切记使用 移除数据处理委托防止内存泄露 - /// - /// 实例 - /// 数据处理实例 - /// 回调方法 - public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func, ValueTask> callback) - { - client.AddDataPackageAdapter(new DataPackageAdapter(handler), callback); - } - - private static readonly Dictionary, ValueTask> ReceivedCallback, Delegate EntityCallback)>> EntityCache = []; - - /// - /// Configures the specified to use a data package adapter and a callback function - /// for processing received data. 切记使用 移除数据处理委托防止内存泄露 - /// - /// This method sets up the to process incoming data using the - /// specified and . The is called with the converted entity whenever data is received. - /// The type of the entity that the data will be converted to. - /// The TCP socket client to configure. - /// The data package adapter responsible for handling incoming data. - /// The converter used to transform the received data into the specified entity type. - /// The callback function to be invoked with the converted entity. - public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter socketDataConverter, Func callback) - { - async ValueTask ReceivedCallback(ReadOnlyMemory buffer) - { - // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 - await adapter.HandlerAsync(buffer); - } - - if (EntityCache.TryGetValue(client, out var list)) - { - list.Add((ReceivedCallback, callback)); - } - else - { - EntityCache.Add(client, [(ReceivedCallback, callback)]); - } - - client.ReceivedCallback += ReceivedCallback; - - // 设置 DataPackageAdapter 的回调函数 - adapter.ReceivedCallBack = async buffer => - { - TEntity? ret = default; - if (socketDataConverter.TryConvertTo(buffer, out var t)) - { - ret = t; - } - await callback(ret); - }; - } - - /// - /// 移除 数据适配器及其对应的回调方法 - /// - /// - /// - public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func callback) - { - if (EntityCache.TryGetValue(client, out var list)) - { - var items = list.Where(i => i.EntityCallback.Equals(callback)).ToList(); - foreach (var c in items) - { - client.ReceivedCallback -= c.ReceivedCallback; - list.Remove(c); - } - } - } - - /// - /// 通过指定 数据处理实例,设置数据适配器并配置回调方法。切记使用 移除数据处理委托防止内存泄露 - /// - /// - /// - /// - /// - /// - public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, IDataConverter socketDataConverter, Func callback) - { - client.AddDataPackageAdapter(new DataPackageAdapter(handler), socketDataConverter, callback); - } - - /// - /// Configures the specified to use a custom data package adapter and callback - /// function. 切记使用 移除数据处理委托防止内存泄露 - /// - /// 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 - /// the . The callback is called with the converted entity or if - /// conversion fails. - /// The type of entity that the data package adapter will handle. - /// The TCP socket client to configure. - /// The data package adapter responsible for processing incoming data. - /// The callback function to invoke with the processed entity of type . - public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func callback) - { - async ValueTask ReceivedCallback(ReadOnlyMemory buffer) - { - // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 - await adapter.HandlerAsync(buffer); - } - - if (EntityCache.TryGetValue(client, out var list)) - { - list.Add((ReceivedCallback, callback)); - } - else - { - EntityCache.Add(client, [(ReceivedCallback, callback)]); - } - - client.ReceivedCallback += ReceivedCallback; - - IDataConverter? converter = null; - - var type = typeof(TEntity); - var converterType = type.GetCustomAttribute(); - - // 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器 - // 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器 - converter = converterType is { Type: not null } - ? converterType.Type.CreateInstance>() - : client.GetSocketDataConverter(); - - if (converter == null) - { - // 未设置数据转换器返回 default 值 - adapter.ReceivedCallBack = async buffer => await callback(default); - } - else - { - // 设置转化器 - adapter.ReceivedCallBack = async buffer => - { - TEntity? ret = default; - if (converter.TryConvertTo(buffer, out var t)) - { - ret = t; - } - await callback(ret); - }; - } - } - - /// - /// 通过指定 数据处理实例,设置数据适配器并配置回调方法。切记使用 移除数据处理委托防止内存泄露 - /// - /// 实例 - /// 数据处理实例 - /// 回调方法 - public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func callback) - { - client.AddDataPackageAdapter(new DataPackageAdapter(handler), callback); - } - - private static IDataConverter? GetSocketDataConverter(this ITcpSocketClient client) - { - IDataConverter? 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/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs deleted file mode 100644 index e3d2cbf9..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Runtime.Versioning; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// TcpSocket 扩展方法 -/// -[UnsupportedOSPlatform("browser")] -public static class TcpSocketExtensions -{ - /// - /// 增加 ITcpSocketFactory 服务 - /// - /// - /// - public static IServiceCollection AddBootstrapBlazorTcpSocketFactory(this IServiceCollection services) - { - // 添加 ITcpSocketFactory 服务 - services.AddSingleton(); - - // 增加 ISocketClientProvider 服务 - services.TryAddTransient(); - - return services; - } - - /// - /// 配置第三方数据模型与 数据转换器集合配置扩展方法 - /// - /// - /// - /// - public static IServiceCollection ConfigureDataConverters(this IServiceCollection services, Action configureOptions) - { - services.Configure(configureOptions); - return services; - } -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs b/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs deleted file mode 100644 index 3c563331..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/Extensions/TcpSocketUtility.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; -using System.Net.Sockets; -using System.Runtime.Versioning; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// SocketUtility 帮助类 -/// -public static class TcpSocketUtility -{ - /// - /// Converts a string representation of an IP address or hostname into an object. - /// - /// This method handles common special cases for IP address strings, such as "localhost" and - /// "any". For other inputs, it attempts to parse the string as an IP address using . If parsing fails, the method resolves the input as a - /// hostname. - /// A string containing the IP address or hostname to convert. Special values include: - /// "localhost" returns the loopback address (). "any" returns the wildcard address - /// (). For other values, the method attempts to parse the - /// string as an IP address or resolve it as a hostname. - /// An object representing the parsed or resolved IP address. If the input cannot be parsed - /// or resolved, the method returns a default IP address. - [UnsupportedOSPlatform("browser")] - public static IPAddress ConvertToIPAddress(string ipString) - { - if (string.IsNullOrEmpty(ipString)) - { - throw new ArgumentNullException(nameof(ipString), "IP address cannot be null or empty."); - } - - if (ipString.Equals("localhost", StringComparison.OrdinalIgnoreCase)) - { - return IPAddress.Loopback; - } - if (ipString.Equals("any", StringComparison.OrdinalIgnoreCase)) - { - return IPAddress.Any; - } - - return IPAddress.TryParse(ipString, out var ip) ? ip : IPAddressByHostName; - } - - [ExcludeFromCodeCoverage] - [UnsupportedOSPlatform("browser")] - private static IPAddress IPAddressByHostName => Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Any; - - /// - /// Converts a string representation of an IP address and a port number into an instance. - /// - /// This method is not supported on browser platforms. - /// The string representation of the IP address. Must be a valid IPv4 or IPv6 address. - /// The port number associated with the endpoint. Must be between 0 and 65535. - /// An representing the specified IP address and port. - /// Thrown if is less than 0 or greater than 65535. - [UnsupportedOSPlatform("browser")] - public static IPEndPoint ConvertToIpEndPoint(string ipString, int port) - { - if (port < 0 || port > 65535) - { - throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 0 and 65535."); - } - - var address = ConvertToIPAddress(ipString); - return new IPEndPoint(address, port); - } -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs deleted file mode 100644 index 5ad2f777..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClient.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// Represents a TCP socket for network communication. -/// -public interface ITcpSocketClient : IAsyncDisposable -{ - /// - /// Gets a value indicating whether the system is currently connected. Default is false. - /// - bool IsConnected { get; } - - /// - /// Gets or sets the configuration options for the socket client. - /// - TcpSocketClientOptions Options { get; } - - /// - /// Gets the local network endpoint that the socket is bound to. - /// - /// This property provides information about the local endpoint of the socket, which is typically - /// used to identify the local address and port being used for communication. If the socket is not bound to a - /// specific local endpoint, this property may return . - IPEndPoint LocalEndPoint { get; } - - /// - /// Gets or sets the callback function to handle received data. - /// - /// The callback function should be designed to handle the received data efficiently and - /// asynchronously. Ensure that the implementation does not block or perform long-running operations, as this may - /// impact performance. - Func, ValueTask>? ReceivedCallback { get; set; } - - /// - /// Gets or sets the callback function that is invoked when a connection attempt is initiated. - /// - Func? OnConnecting { get; set; } - - /// - /// Gets or sets the delegate to be invoked when a connection is successfully established. - /// - Func? OnConnected { get; set; } - - /// - /// Establishes an asynchronous connection to the specified endpoint. - /// - /// This method attempts to establish a connection to the specified endpoint. If the connection - /// cannot be established, the method returns rather than throwing an exception. - /// The representing the remote endpoint to connect to. Cannot be null. - /// A that can be used to cancel the connection attempt. Defaults to if not provided. - /// A task that represents the asynchronous operation. The task result is if the connection - /// is successfully established; otherwise, . - ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default); - - /// - /// Sends the specified data asynchronously to the target endpoint. - /// - /// This method performs a non-blocking operation to send data. If the operation is canceled via - /// the , the task will complete with a canceled state. Ensure the connection is properly - /// initialized before calling this method. - /// The byte array containing the data to be sent. Cannot be null or empty. - /// An optional to observe while waiting for the operation to complete. - /// A task that represents the asynchronous operation. The task result is if the data was - /// sent successfully; otherwise, . - ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default); - - /// - /// Asynchronously receives a block of data from the underlying source. - /// - /// This method is non-blocking and completes when data is available or the operation is - /// canceled. If the operation is canceled, the returned task will be in a faulted state with a . - /// A cancellation token that can be used to cancel the operation. The default value is . - /// A containing a of bytes representing the received data. - /// The returned memory may be empty if no data is available. - ValueTask> ReceiveAsync(CancellationToken token = default); - - /// - /// Closes the current connection or resource, releasing any associated resources. - /// - /// Once the connection or resource is closed, it cannot be reopened. Ensure that all necessary - /// operations are completed before calling this method. This method is typically used to clean up resources when - /// they are no longer needed. - ValueTask CloseAsync(); -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs deleted file mode 100644 index 00b82018..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketClientProvider.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// Defines the contract for a socket client that provides asynchronous methods for connecting, sending, receiving, and -/// closing network connections. -/// -/// This interface is designed to facilitate network communication using sockets. It provides methods for -/// establishing connections, transmitting data, and receiving data asynchronously. Implementations of this interface -/// should ensure proper resource management, including closing connections and releasing resources when no longer -/// needed. -public interface ITcpSocketClientProvider -{ - /// - /// Gets a value indicating whether the connection is currently active. - /// - bool IsConnected { get; } - - /// - /// Gets the local network endpoint that the socket is bound to. - /// - IPEndPoint LocalEndPoint { get; set; } - - /// - /// Establishes an asynchronous connection to the specified endpoint. - /// - /// This method attempts to establish a connection to the specified endpoint. If the connection - /// fails, the method returns rather than throwing an exception. Ensure the endpoint is - /// valid and reachable before calling this method. - /// The representing the remote endpoint to connect to. - /// An optional to observe while waiting for the connection to complete. - /// A that represents the asynchronous operation. The result is if the connection was successfully established; otherwise, . - ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default); - - /// - /// Sends the specified data asynchronously to the connected endpoint. - /// - /// This method performs a non-blocking operation to send data. If the operation is canceled via - /// the , the returned task will not complete successfully. Ensure the connected endpoint - /// is ready to receive data before calling this method. - /// The data to send, represented as a read-only memory block of bytes. - /// An optional cancellation token that can be used to cancel the operation. - /// A representing the asynchronous operation. The result is if the data was sent successfully; otherwise, . - ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default); - - /// - /// Asynchronously receives data from a source and writes it into the specified buffer. - /// - /// This method does not guarantee that the buffer will be completely filled. The caller should - /// check the return value to determine the number of bytes received. - /// The memory buffer where the received data will be stored. Must be large enough to hold the incoming data. - /// A cancellation token that can be used to cancel the operation. Defaults to if not - /// provided. - /// A representing the asynchronous operation. The result is the number of bytes - /// successfully received and written into the buffer. Returns 0 if the end of the data stream is reached. - ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default); - - /// - /// Closes the current connection or resource, releasing any associated resources. - /// - /// Once the connection or resource is closed, it cannot be reopened. Ensure that all necessary - /// operations are completed before calling this method. This method is typically used to clean up resources when - /// they are no longer needed. - ValueTask CloseAsync(); -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs b/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs deleted file mode 100644 index c97065a3..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/ITcpSocketFactory.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace BootstrapBlazor.TcpSocket; - -/// -/// ITcpSocketFactory Interface -/// -public interface ITcpSocketFactory : IAsyncDisposable -{ - /// - /// Retrieves an existing TCP socket client by name or creates a new one using the specified configuration. - /// - /// The unique name of the TCP socket client to retrieve or create. Cannot be null or empty. - /// A delegate used to configure the for the new TCP socket client if it does not - /// already exist. This delegate is invoked only when a new client is created. - /// An instance of corresponding to the specified name. If the client already exists, - /// the existing instance is returned; otherwise, a new instance is created and returned. - ITcpSocketClient GetOrCreate(string name, Action valueFactory); - - /// - /// Removes the TCP socket client associated with the specified name. - /// - /// The name of the TCP socket client to remove. Cannot be or empty. - /// The removed instance if a client with the specified name exists; otherwise, . - ITcpSocketClient? Remove(string name); -} diff --git a/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs b/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs deleted file mode 100644 index 88907b1e..00000000 --- a/src/extensions/BootstrapBlazor.TcpSocket/TcpSocketClientOptions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; - -namespace BootstrapBlazor.TcpSocket; - -/// -/// Represents configuration options for a socket client, including buffer sizes, timeouts, and endpoints. -/// -/// Use this class to configure various settings for a socket client, such as connection timeouts, -/// buffer sizes, and local or remote endpoints. These options allow fine-tuning of socket behavior to suit specific -/// networking scenarios. -public class TcpSocketClientOptions -{ - /// - /// Gets or sets the size, in bytes, of the receive buffer used by the connection. - /// - public int ReceiveBufferSize { get; set; } = 1024 * 64; - - /// - /// Gets or sets a value indicating whether automatic receiving data is enabled. Default is true. - /// - public bool IsAutoReceive { get; set; } = true; - - /// - /// Gets or sets the timeout duration, in milliseconds, for establishing a connection. - /// - public int ConnectTimeout { get; set; } - - /// - /// Gets or sets the duration, in milliseconds, to wait for a send operation to complete before timing out. - /// - /// If the send operation does not complete within the specified timeout period, an exception may - /// be thrown. - public int SendTimeout { get; set; } - - /// - /// Gets or sets the amount of time, in milliseconds, that the receiver will wait for a response before timing out. - /// - /// Use this property to configure the maximum wait time for receiving a response. Setting an - /// appropriate timeout can help prevent indefinite blocking in scenarios where responses may be delayed or - /// unavailable. - public int ReceiveTimeout { get; set; } - - /// - /// Gets or sets the local endpoint for the socket client. Default value is - /// - /// This property specifies the local network endpoint that the socket client will bind to when establishing a connection. - public IPEndPoint LocalEndPoint { get; set; } = new(IPAddress.Any, 0); - - /// - /// Gets or sets a value indicating whether logging is enabled. Default value is false. - /// - public bool EnableLog { get; set; } - - /// - /// Gets or sets a value indicating whether the system should automatically attempt to reconnect after a connection is lost. Default value is false. - /// - public bool IsAutoReconnect { get; set; } - - /// - /// Gets or sets the interval, in milliseconds, between reconnection attempts. Default value is 5000. - /// - public int ReconnectInterval { get; set; } = 5000; -} diff --git a/test/UnitTestTcpSocket/ActivationExtensionsTest.cs b/test/UnitTestTcpSocket/ActivationExtensionsTest.cs deleted file mode 100644 index 3de407d1..00000000 --- a/test/UnitTestTcpSocket/ActivationExtensionsTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace UnitTestTcpSocket; - -public class ActivationExtensionsTest -{ - [Fact] - public void Activation_Ok() - { - var type = typeof(Foo); - var o = type.CreateInstance(); - Assert.NotNull(o); - - var foo = o as Foo; - Assert.NotNull(foo); - - var foo1 = type.CreateInstance(); - Assert.NotNull(foo1); - } - - [Fact] - public void Activation_Nest() - { - var o = typeof(MockNestEntity).CreateInstance([0.01f]); - Assert.Equal(0.01f, o?.Rate); - } - - [Fact] - public void Activation_Fail() - { - var type = typeof(string); - var o = type.CreateInstance([123]); - Assert.Null(o); - - var foo = type.CreateInstance(); - Assert.Null(foo); - } - - class MockNestEntity(float rate) - { - public float Rate { get; } = rate; - } -} diff --git a/test/UnitTestTcpSocket/Assembly.cs b/test/UnitTestTcpSocket/Assembly.cs deleted file mode 100644 index 2eac8083..00000000 --- a/test/UnitTestTcpSocket/Assembly.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -[assembly: ExcludeFromCodeCoverage] diff --git a/test/UnitTestTcpSocket/BinConverterTest.cs b/test/UnitTestTcpSocket/BinConverterTest.cs deleted file mode 100644 index d8607c72..00000000 --- a/test/UnitTestTcpSocket/BinConverterTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace UnitTestTcpSocket; - -public class BinConverterTest -{ - [Fact] - public void ToHexString_Null() - { - var actual = BinConverter.ToString(null); - Assert.Equal(string.Empty, actual); - - actual = BinConverter.ToString([]); - Assert.Equal(string.Empty, actual); - } - - [Fact] - public void ToBinString_Ok() - { - var data = new byte[] { 0x1A, 0x02 }; - var actual = BinConverter.ToString(data); - Assert.Equal("00011010-00000010", actual); - - actual = BinConverter.ToString(data, " "); - Assert.Equal("00011010 00000010", actual); - } - - [Fact] - public void ToHexString_Exception() - { - var data = "00011010-00000010"; - var ex = Assert.ThrowsAny(() => BinConverter.ToBytes(data)); - Assert.NotNull(ex); - } - - [Fact] - public void ToBytes_Ok() - { - var excepted = new byte[] { 0x1A, 0x02 }; - - var data = "00011010-00000010"; - var actual = BinConverter.ToBytes(data, "-"); - Assert.Equal(excepted, actual); - - data = "00011010 00000010"; - actual = BinConverter.ToBytes(data, " "); - Assert.Equal(excepted, actual); - - data = "0001101000000010"; - actual = BinConverter.ToBytes(data); - Assert.Equal(excepted, actual); - } -} diff --git a/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs b/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs deleted file mode 100644 index 61658a22..00000000 --- a/test/UnitTestTcpSocket/DefaultSocketClientProviderTest.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; -using System.Net.Sockets; - -namespace UnitTestTcpSocket; - -public class DefaultSocketClientProviderTest -{ - [Fact] - public async Task DefaultSocketClient_Ok() - { - var sc = new ServiceCollection(); - sc.AddBootstrapBlazorTcpSocketFactory(); - var provider = sc.BuildServiceProvider(); - var clientProvider = provider.GetRequiredService(); - - // 未建立连接时 IsConnected 应为 false - Assert.False(clientProvider.IsConnected); - - // 未建立连接直接调用 ReceiveAsync 方法 - var buffer = new byte[1024]; - var len = await clientProvider.ReceiveAsync(buffer); - Assert.Equal(0, len); - } - - [Fact] - public async Task ReceiveAsync_Ok() - { - var port = 8100; - // 测试接收数据时服务器断开未连接的情况 - StartTcpServer(port); - - var sc = new ServiceCollection(); - sc.AddBootstrapBlazorTcpSocketFactory(); - var provider = sc.BuildServiceProvider(); - var factory = provider.GetRequiredService(); - var client = factory.GetOrCreate("provider", op => - { - op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0); - op.IsAutoReceive = false; - op.EnableLog = false; - }); - - await client.ConnectAsync("127.0.0.1", port); - Assert.True(client.IsConnected); - - var buffer = await client.ReceiveAsync(); - Assert.Equal(2, buffer.Length); - - await Task.Delay(50); - buffer = await client.ReceiveAsync(); - Assert.False(client.IsConnected); - } - - [Fact] - public void SocketClientOptions_Ok() - { - var options = new TcpSocketClientOptions - { - ReceiveBufferSize = 1024 * 64, - IsAutoReceive = true, - ConnectTimeout = 1000, - SendTimeout = 500, - ReceiveTimeout = 500, - LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0) - }; - Assert.Equal(1024 * 64, options.ReceiveBufferSize); - Assert.True(options.IsAutoReceive); - Assert.Equal(1000, options.ConnectTimeout); - Assert.Equal(500, options.SendTimeout); - Assert.Equal(500, options.ReceiveTimeout); - Assert.Equal(new IPEndPoint(IPAddress.Loopback, 0), options.LocalEndPoint); - } - - private static TcpListener StartTcpServer(int port) - { - var server = new TcpListener(IPAddress.Loopback, port); - server.Start(); - Task.Run(() => AcceptClientsAsync(server)); - return server; - } - - private static async Task AcceptClientsAsync(TcpListener server) - { - while (true) - { - var client = await server.AcceptTcpClientAsync(); - _ = Task.Run(async () => - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[1024]; - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4 }, CancellationToken.None); - - // 等待 20ms - await Task.Delay(20); - client.Close(); - } - }); - } - } -} diff --git a/test/UnitTestTcpSocket/Foo.cs b/test/UnitTestTcpSocket/Foo.cs deleted file mode 100644 index e244b923..00000000 --- a/test/UnitTestTcpSocket/Foo.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.ComponentModel.DataAnnotations; - -namespace UnitTestTcpSocket; - -/// -/// Demo示例数据 -/// Demo sample data -/// -public class Foo -{ - // 列头信息支持 Display DisplayName 两种标签 - - /// - /// 主键 - /// - [Key] - [Display(Name = "主键")] - public int Id { get; set; } - - /// - /// 姓名 - /// - [Required(ErrorMessage = "{0}不能为空")] - [Display(Name = "姓名")] - public string? Name { get; set; } - - /// - /// 日期 - /// - [Display(Name = "日期")] - public DateTime? DateTime { get; set; } - - /// - /// 地址 - /// - [Display(Name = "地址")] - [Required(ErrorMessage = "{0}不能为空")] - public string? Address { get; set; } - - /// - /// 数量 - /// - [Display(Name = "数量")] - [Required] - public int Count { get; set; } - - /// - /// 是/否 - /// - [Display(Name = "是/否")] - public bool Complete { get; set; } - - /// - /// 学历 - /// - [Required(ErrorMessage = "请选择学历")] - [Display(Name = "学历")] - public EnumEducation? Education { get; set; } - - /// - /// 爱好 - /// - [Required(ErrorMessage = "请选择一种{0}")] - [Display(Name = "爱好")] - public IEnumerable Hobby { get; set; } = new List(); - - /// - /// 只读列,模拟数据库计算列 - /// - [Display(Name = "只读列")] - public int ReadonlyColumn { get; init; } -} - -/// -/// -/// -public enum EnumEducation -{ - /// - /// - /// - [Display(Name = "小学")] - Primary, - - /// - /// - /// - [Display(Name = "中学")] - Middle -} diff --git a/test/UnitTestTcpSocket/HexConverterTest.cs b/test/UnitTestTcpSocket/HexConverterTest.cs deleted file mode 100644 index 4c95a1db..00000000 --- a/test/UnitTestTcpSocket/HexConverterTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace UnitTestTcpSocket; - -public class HexConverterTest -{ - [Fact] - public void ToHexString_Null() - { - var actual = HexConverter.ToString(null); - Assert.Equal(string.Empty, actual); - - actual = HexConverter.ToString([]); - Assert.Equal(string.Empty, actual); - } - - [Fact] - public void ToHexString_Exception() - { - var data = "1A021304FE1"; - var ex = Assert.ThrowsAny(() => HexConverter.ToBytes(data)); - Assert.NotNull(ex); - } - - [Fact] - public void ToHexString_Ok() - { - var data = new byte[] { 0x1A, 0x02, 0x13, 0x04, 0xFE }; - var actual = HexConverter.ToString(data); - Assert.Equal("1A-02-13-04-FE", actual); - - actual = HexConverter.ToString(data, " ", false); - Assert.Equal("1a 02 13 04 fe", actual); - - actual = HexConverter.ToString(data, " ", true); - Assert.Equal("1A 02 13 04 FE", actual); - } - - [Fact] - public void ToBytes_Ok() - { - var excepted = new byte[] { 0x1A, 0x02, 0x13, 0x04, 0xFE }; - - var data = "1A021304FE"; - var actual = HexConverter.ToBytes(data); - Assert.Equal(excepted, actual); - - data = "1A-02-13-04-FE"; - actual = HexConverter.ToBytes(data, "-"); - Assert.Equal(excepted, actual); - - data = "1A 02 13 04 FE"; - actual = HexConverter.ToBytes(data, " "); - Assert.Equal(excepted, actual); - } -} diff --git a/test/UnitTestTcpSocket/ModbusCrcTest.cs b/test/UnitTestTcpSocket/ModbusCrcTest.cs deleted file mode 100644 index fc1b5b7f..00000000 --- a/test/UnitTestTcpSocket/ModbusCrcTest.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Socket.Algorithm; - -namespace UnitTestTcpSocket; - -public class ModbusCrcTest -{ - [Fact] - public void Computer_Ok() - { - // 06 00 00 01 7F C9 BA - var data = new byte[] { 0x01, 0x06, 0x00, 0x00, 0x01, 0x7F }; - - var crc = ModbusCrc16.Compute(data); - Assert.Equal("BAC9", crc.ToString("X4")); - Assert.Equal("01060000017FC9BA", HexConverter.ToString(ModbusCrc16.Append(data), "")); - } - - [Fact] - public void Validate_Ok() - { - var result = ModbusCrc16.Validate([0x01]); - Assert.False(result); - - result = ModbusCrc16.Validate([0x01, 0x06, 0x00, 0x00, 0x01, 0x7F, 0xC9, 0xBA]); - Assert.True(result); - - result = false; - var data = Enumerable.Range(0, 300).Select(i => (byte)Random.Shared.Next(0, 255)); - result = ModbusCrc16.Validate(ModbusCrc16.Append(data.ToArray())); - Assert.True(result); - } -} diff --git a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs b/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs deleted file mode 100644 index 0be74daa..00000000 --- a/test/UnitTestTcpSocket/SocketDataConverterCollectionsTest.cs +++ /dev/null @@ -1,102 +0,0 @@ -// 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 UnitTestTcpSocket; - -public class SocketDataConverterCollectionsTest -{ - [Fact] - public void TryGetConverter_Ok() - { - var sc = new ServiceCollection(); - sc.ConfigureDataConverters(options => - { - options.AddTypeConverter(); - options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() - { - Offset = 0, - Length = 5 - }); - options.AddPropertyConverter(entity => entity.Body, new DataPropertyConverterAttribute() - { - Offset = 5, - Length = 2 - }); - - // 为提高代码覆盖率 重复添加转换器以后面的为准 - options.AddTypeConverter(); - options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() - { - Offset = 0, - Length = 5 - }); - }); - - var provider = sc.BuildServiceProvider(); - var service = provider.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/UnitTestTcpSocket/SocketLoggingTest.cs b/test/UnitTestTcpSocket/SocketLoggingTest.cs deleted file mode 100644 index 9fd34ec2..00000000 --- a/test/UnitTestTcpSocket/SocketLoggingTest.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Socket.Logging; - -namespace UnitTestTcpSocket; - -public class SocketLoggingTest -{ - [Fact] - public void Logger_Ok() - { - SocketLogging.LogError(new Exception()); - SocketLogging.LogInformation("Information"); - SocketLogging.LogWarning("Warning"); - SocketLogging.LogDebug("Debug"); - } -} diff --git a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs b/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs deleted file mode 100644 index 108d0bc4..00000000 --- a/test/UnitTestTcpSocket/TcpSocketFactoryTest.cs +++ /dev/null @@ -1,1488 +0,0 @@ -// Copyright (c) BootstrapBlazor & Argo Zhang (argo@live.ca). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Socket.Logging; -using Microsoft.Extensions.Logging; -using System.Buffers; -using System.Net; -using System.Net.Sockets; -using System.Reflection; -using System.Text; - -namespace UnitTestTcpSocket; - -public class TcpSocketFactoryTest -{ - [Fact] - public async Task GetOrCreate_Ok() - { - // 测试 GetOrCreate 方法创建的 Client 销毁后继续 GetOrCreate 得到的对象是否可用 - var sc = new ServiceCollection(); - sc.AddLogging(builder => - { - builder.AddProvider(new MockLoggerProvider()); - }); - sc.AddBootstrapBlazorTcpSocketFactory(); - var provider = sc.BuildServiceProvider(); - var factory = provider.GetRequiredService(); - var client1 = factory.GetOrCreate("demo", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0)); - await client1.CloseAsync(); - - var client2 = factory.GetOrCreate("demo", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0)); - Assert.Equal(client1, client2); - - var ip = Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback; - var client3 = factory.GetOrCreate("demo1", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint(ip.ToString(), 0)); - - // 测试不合格 IP 地址 - var client4 = factory.GetOrCreate("demo2", op => op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("256.0.0.1", 0)); - - var client5 = factory.Remove("demo2"); - Assert.Equal(client4, client5); - Assert.NotNull(client5); - - await client5.DisposeAsync(); - await factory.DisposeAsync(); - - SocketLogging.LogWarning("Warning"); - SocketLogging.LogDebug("Debug"); - SocketLogging.LogInformation("Information"); - } - - [Fact] - public async Task ConnectAsync_Timeout() - { - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(); - }); - client.Options.ConnectTimeout = 10; - - var connect = await client.ConnectAsync("localhost", 9999); - Assert.False(connect); - } - - [Fact] - public async Task ConnectAsync_Cancel() - { - var client = CreateClient(builder => - { - builder.AddTransient(); - }, - options => - { - options.ConnectTimeout = 500; - }); - - // 测试 ConnectAsync 方法连接取消逻辑 - var cst = new CancellationTokenSource(); - cst.Cancel(); - var connect = await client.ConnectAsync("localhost", 9999, cst.Token); - - // 由于信号量被取消,所以连接会失败 - Assert.False(connect); - - // 测试真正的连接被取消逻辑 - cst = new CancellationTokenSource(200); - connect = await client.ConnectAsync("localhost", 9999, cst.Token); - Assert.False(connect); - } - - [Fact] - public async Task ConnectAsync_Failed() - { - var client = CreateClient(); - - // 测试 ConnectAsync 方法连接失败 - var connect = await client.ConnectAsync("localhost", 9999); - Assert.False(connect); - } - - [Fact] - public async Task ConnectAsync_Error() - { - var client = CreateClient(); - - // 反射设置 SocketClientProvider 为空 - var propertyInfo = client.GetType().GetProperty("ServiceProvider", BindingFlags.Public | BindingFlags.Instance); - Assert.NotNull(propertyInfo); - propertyInfo.SetValue(client, null); - - // 测试 ConnectAsync 方法连接失败 - var ex = await Assert.ThrowsAsync(async () => await client.ConnectAsync("localhost", 9999)); - Assert.NotNull(ex); - - // 反射测试 Log 方法 - var methodInfo = client.GetType().GetMethod("Log", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(methodInfo); - methodInfo.Invoke(client, [LogLevel.Error, null!, "Test error log"]); - } - - [Fact] - public async Task ConnectAsync_Lock() - { - // 测试并发锁问题 - var provider = new MockAutoReconnectLockSocketProvider(); - var client = CreateClient(builder => - { - builder.AddTransient(p => provider); - }); - - // 开 5 个线程同时连接 - _ = Task.Run(async () => - { - // 延时 150 保证有一个连接失败 - await Task.Delay(150); - provider.SetConnected(true); - }); - var results = await Task.WhenAll(Enumerable.Range(1, 5).Select(i => client.ConnectAsync("localhost", 0).AsTask())); - // 期望结果是 1个 false 4个 true - Assert.Equal(1, results.Count(r => !r)); - Assert.Equal(4, results.Count(r => r)); - } - - [Fact] - public async Task Send_Timeout() - { - var port = 8887; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(); - }); - client.Options.SendTimeout = 10; - - await client.ConnectAsync("localhost", port); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var result = await client.SendAsync(data); - Assert.False(result); - } - - [Fact] - public async Task SendAsync_Error() - { - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(); - }); - - // 测试未建立连接前调用 SendAsync 方法报异常逻辑 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var ex = await Assert.ThrowsAsync(async () => await client.SendAsync(data)); - Assert.NotNull(ex); - - // 测试发送失败 - var port = 8892; - var server = StartTcpServer(port, MockSplitPackageAsync); - - await client.ConnectAsync("localhost", port); - Assert.True(client.IsConnected); - - // 内部生成异常日志 - await client.SendAsync(data); - } - - [Fact] - public async Task SendAsync_Cancel() - { - var port = 8881; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - Assert.False(client.IsConnected); - - // 连接 TCP Server - await client.ConnectAsync("localhost", port); - Assert.True(client.IsConnected); - - // 测试 SendAsync 方法发送取消逻辑 - var cst = new CancellationTokenSource(); - cst.Cancel(); - - var result = await client.SendAsync("test", null, cst.Token); - Assert.False(result); - - result = await client.SendAsync("test", Encoding.UTF8, cst.Token); - Assert.False(result); - - // 关闭连接 - StopTcpServer(server); - } - - [Fact] - public async Task ReceiveAsync_Timeout() - { - var port = 8888; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - client.Options.ReceiveTimeout = 100; - - await client.ConnectAsync("localhost", port); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - await Task.Delay(220); // 等待接收超时 - } - - [Fact] - public async Task ReceiveAsync_Cancel() - { - var port = 8889; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - await client.ConnectAsync("localhost", port); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 通过反射取消令牌 - var type = client.GetType(); - Assert.NotNull(type); - - var fieldInfo = type.GetField("_receiveCancellationTokenSource", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(fieldInfo); - var tokenSource = fieldInfo.GetValue(client) as CancellationTokenSource; - Assert.NotNull(tokenSource); - tokenSource.Cancel(); - await Task.Delay(50); - } - - [Fact] - public async Task ReceiveAsync_InvalidOperationException() - { - // 未连接时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常 - var client = CreateClient(); - var ex = await Assert.ThrowsAsync(async () => await client.ReceiveAsync()); - Assert.NotNull(ex); - - // 已连接但是启用了自动接收功能时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常 - var port = 8893; - var server = StartTcpServer(port, MockSplitPackageAsync); - - client.Options.IsAutoReceive = true; - var connected = await client.ConnectAsync("localhost", port); - Assert.True(connected); - - ex = await Assert.ThrowsAsync(async () => await client.ReceiveAsync()); - Assert.NotNull(ex); - } - - [Fact] - public async Task ReceiveAsync_Ok() - { - var onConnecting = false; - var onConnected = false; - var port = 8891; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - client.Options.IsAutoReceive = false; - client.OnConnecting = () => - { - onConnecting = true; - return Task.CompletedTask; - }; - client.OnConnected = () => - { - onConnected = true; - return Task.CompletedTask; - }; - var connected = await client.ConnectAsync("localhost", port); - Assert.True(connected); - Assert.True(onConnecting); - Assert.True(onConnected); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var send = await client.SendAsync(data); - Assert.True(send); - - // 未设置数据处理器未开启自动接收时,调用 ReceiveAsync 方法获取数据 - // 需要自己处理粘包分包和业务问题 - var payload = await client.ReceiveAsync(); - Assert.Equal([1, 2, 3, 4, 5], payload.ToArray()); - - // 由于服务器端模拟了拆包发送第二段数据,所以这里可以再次调用 ReceiveAsync 方法获取第二段数据 - payload = await client.ReceiveAsync(); - Assert.Equal([3, 4], payload.ToArray()); - } - - [Fact] - public async Task ReceiveAsync_Error() - { - var client = CreateClient(); - - // 测试未建立连接前调用 ReceiveAsync 方法报异常逻辑 - var type = client.GetType(); - Assert.NotNull(type); - - var methodInfo = type.GetMethod("AutoReceiveAsync", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(methodInfo); - - var task = (ValueTask)methodInfo.Invoke(client, null)!; - var ex = await Assert.ThrowsAsync(async () => await task); - Assert.NotNull(ex); - - var port = 8882; - var server = StartTcpServer(port, MockSplitPackageAsync); - - Assert.Equal(1024 * 64, client.Options.ReceiveBufferSize); - - client.Options.ReceiveBufferSize = 1024 * 20; - Assert.Equal(1024 * 20, client.Options.ReceiveBufferSize); - - ReadOnlyMemory buffer = ReadOnlyMemory.Empty; - var tcs = new TaskCompletionSource(); - - // 增加接收回调方法 - client.ReceivedCallback = b => - { - buffer = b; - tcs.SetResult(); - return ValueTask.CompletedTask; - }; - - await client.ConnectAsync("localhost", port); - - // 发送数据导致接收数据异常 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - await tcs.Task; - Assert.Equal([1, 2, 3, 4, 5], buffer.ToArray()); - - // 关闭连接 - StopTcpServer(server); - } - - [Fact] - public async Task AutoReconnect_Ok() - { - var client = CreateClient(optionConfigure: options => - { - options.IsAutoReconnect = true; - options.ReconnectInterval = 200; - options.IsAutoReceive = true; - }); - - // 使用场景自动接收数据,短线后自动重连 - var port = 8894; - var connect = await client.ConnectAsync("localhost", port); - Assert.False(connect); - - // 开启服务端后,可以自动重连上 - var server = StartTcpServer(port, LoopSendPackageAsync); - await Task.Delay(250); - Assert.True(client.IsConnected); - - await client.DisposeAsync(); - } - - [Fact] - public async Task AutoReconnect_False() - { - var provider = new MockAutoReconnectSocketProvider(); - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(p => provider); - }, - optionConfigure: options => - { - options.IsAutoReconnect = true; - options.ReconnectInterval = 200; - options.IsAutoReceive = true; - }); - - // 使用场景自动接收数据,短线后自动重连 - var connect = await client.ConnectAsync("localhost", 0); - Assert.False(connect); - - provider.SetConnected(true); - await Task.Delay(250); - Assert.True(client.IsConnected); - } - - [Fact] - public async Task AutoReconnect_Send_Ok() - { - // 发送数据时连接断开了,测试重连功能 - var provider = new MockAutoReconnectSocketProvider(); - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(p => provider); - }, optionConfigure: options => - { - options.IsAutoReconnect = true; - options.ReconnectInterval = 200; - options.IsAutoReceive = true; - }); - - provider.SetConnected(true); - var connect = await client.ConnectAsync("localhost", 0); - Assert.True(connect); - - // 发送时断开连接 - provider.SetSend(false); - var send = await client.SendAsync("test"); - Assert.False(send); - - await Task.Delay(250); - Assert.True(client.IsConnected); - } - - [Fact] - public async Task AutoReconnect_Receive_Ok() - { - // 接收数据时连接断开了,测试重连功能 - var provider = new MockAutoReconnectSocketProvider(); - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(p => provider); - }, optionConfigure: options => - { - options.IsAutoReconnect = true; - options.ReconnectInterval = 200; - options.IsAutoReceive = false; - }); - - provider.SetConnected(true); - var connect = await client.ConnectAsync("localhost", 0); - Assert.True(connect); - - // 发送时断开连接 - provider.SetReceive(false); - var buffer = await client.ReceiveAsync(); - Assert.Equal(Memory.Empty, buffer); - - await Task.Delay(250); - provider.SetReceive(true); - buffer = await client.ReceiveAsync(); - Assert.Equal(5, buffer.Length); - } - - [Fact] - public async Task AutoReconnect_Cancel() - { - // 测试重连时取消逻辑 - var provider = new MockAutoReconnectSocketProvider(); - var client = CreateClient(builder => - { - // 增加发送报错 MockSocket - builder.AddTransient(p => provider); - }, optionConfigure: options => - { - options.IsAutoReconnect = true; - options.ReconnectInterval = 2000; - options.IsAutoReceive = false; - }); - - await client.ConnectAsync("localhost", 0); - await Task.Delay(100); - await client.DisposeAsync(); - } - - [Fact] - public async Task FixLengthDataPackageHandler_Ok() - { - var port = 8884; - var server = StartTcpServer(port, MockSplitPackageAsync); - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[1024]; - - // 设置数据适配器 - var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(7)); - var callback = new Func, ValueTask>(buffer => - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer); - receivedBuffer = receivedBuffer[..buffer.Length]; - tcs.SetResult(); - return ValueTask.CompletedTask; - }); - client.AddDataPackageAdapter(adapter, callback); - - // 测试 ConnectAsync 方法 - var connect = await client.ConnectAsync("localhost", port); - Assert.True(connect); - Assert.True(client.IsConnected); - - // 测试 SendAsync 方法 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var result = await client.SendAsync(data); - Assert.True(result); - - await tcs.Task; - Assert.Equal([1, 2, 3, 4, 5, 3, 4], receivedBuffer.ToArray()); - - // 关闭连接 - await client.CloseAsync(); - StopTcpServer(server); - } - - [Fact] - public async Task FixLengthDataPackageHandler_Sticky() - { - var port = 8885; - var server = StartTcpServer(port, MockStickyPackageAsync); - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[128]; - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - // 设置数据适配器 - var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(7)); - client.AddDataPackageAdapter(adapter, buffer => - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer); - receivedBuffer = receivedBuffer[..buffer.Length]; - tcs.SetResult(); - return ValueTask.CompletedTask; - }); - - // 发送数据 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - - // 验证接收到的数据 - Assert.Equal([1, 2, 3, 4, 5, 3, 4], receivedBuffer.ToArray()); - - // 重置接收缓冲区 - receivedBuffer = new byte[1024]; - tcs = new TaskCompletionSource(); - - // 等待第二次数据 - await tcs.Task; - - // 验证第二次收到的数据 - Assert.Equal([2, 2, 3, 4, 5, 6, 7], receivedBuffer.ToArray()); - tcs = new TaskCompletionSource(); - await tcs.Task; - - // 验证第三次收到的数据 - Assert.Equal([3, 2, 3, 4, 5, 6, 7], receivedBuffer.ToArray()); - - // 关闭连接 - await client.CloseAsync(); - StopTcpServer(server); - } - - [Fact] - public async Task DelimiterDataPackageHandler_Ok() - { - var port = 8883; - var server = StartTcpServer(port, MockDelimiterPackageAsync); - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[128]; - - // 设置数据适配器 - var adapter = new DataPackageAdapter(new DelimiterDataPackageHandler([13, 10])); - client.AddDataPackageAdapter(adapter, buffer => - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer); - receivedBuffer = receivedBuffer[..buffer.Length]; - tcs.SetResult(); - return ValueTask.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.Equal([1, 2, 3, 4, 5, 13, 10], receivedBuffer.ToArray()); - - // 等待第二次数据 - receivedBuffer = new byte[1024]; - tcs = new TaskCompletionSource(); - await tcs.Task; - - // 验证接收到的数据 - Assert.Equal([5, 6, 13, 10], receivedBuffer.ToArray()); - - // 关闭连接 - await client.CloseAsync(); - StopTcpServer(server); - - var handler = new DelimiterDataPackageHandler("\r\n"); - var ex = Assert.Throws(() => new DelimiterDataPackageHandler(string.Empty)); - Assert.NotNull(ex); - - ex = Assert.Throws(() => new DelimiterDataPackageHandler(null!)); - Assert.NotNull(ex); - } - - [Fact] - public void TryConvertTo_Error() - { - var converter = new MockErrorDataConverter(); - var result = converter.TryConvertTo(new byte[] { 0x1, 0x2 }, out var entity); - Assert.False(result); - } - - [Fact] - public async Task TryConvertTo_Ok() - { - var port = 8886; - var server = StartTcpServer(port, MockEntityPackageAsync); - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - MockEntity? entity = null; - - // 设置数据适配器 - var adapter = new DataPackageAdapter(new FixLengthDataPackageHandler(29)); - var callback = new Func(t => - { - entity = t; - tcs.SetResult(); - return Task.CompletedTask; - }); - client.AddDataPackageAdapter(adapter, new DataConverter(), callback); - - // 连接 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([1, 2, 3, 4, 5], entity.Header); - Assert.Equal([3, 4], entity.Body); - - // null - Assert.Equal((byte)0x0, entity.Value16); - - // null - Assert.Equal((byte)0x0, entity.Value17); - - // byte - Assert.Equal(0x1, entity.Value15); - - // string - Assert.Equal("1", entity.Value1); - - // string - Assert.Equal("1", entity.Value14); - - // int - Assert.Equal(9, entity.Value2); - - // long - Assert.Equal(16, entity.Value3); - - // double - Assert.Equal(3.14, entity.Value4); - - // single - Assert.NotEqual(0, entity.Value5); - - // short - Assert.Equal(0x23, entity.Value6); - - // ushort - Assert.Equal(0x24, entity.Value7); - - // uint - Assert.Equal((uint)0x25, entity.Value8); - - // ulong - Assert.Equal((ulong)0x26, entity.Value9); - - // bool - Assert.True(entity.Value10); - - // enum - Assert.Equal(EnumEducation.Middle, entity.Value11); - - // foo - Assert.NotNull(entity.Value12); - Assert.Equal(0x29, entity.Value12.Id); - Assert.Equal("test", entity.Value12.Name); - - // no attribute - Assert.Null(entity.Value13); - client.RemoveDataPackageAdapter(callback); - - // 测试 SocketDataConverter 标签功能 - tcs = new TaskCompletionSource(); - client.AddDataPackageAdapter(adapter, t => - { - entity = t; - tcs.SetResult(); - return Task.CompletedTask; - }); - await client.SendAsync(data); - await tcs.Task; - - Assert.NotNull(entity); - Assert.Equal([1, 2, 3, 4, 5], entity.Header); - - // 测试数据适配器直接调用 TryConvertTo 方法转换数据 - var adapter2 = new DataPackageAdapter(); - var result = adapter2.TryConvertTo(data, new DataConverter(), out var t); - Assert.True(result); - Assert.NotNull(t); - Assert.Equal([1, 2, 3, 4, 5], entity.Header); - - // 测试 SetDataPackageAdapter 泛型无标签情况 - tcs = new TaskCompletionSource(); - NoConvertEntity? noConvertEntity = null; - client.AddDataPackageAdapter(adapter, t => - { - noConvertEntity = t; - tcs.SetResult(); - return Task.CompletedTask; - }); - await client.SendAsync(data); - await tcs.Task; - Assert.Null(noConvertEntity); - - 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.Configure(options => - { - options.AddTypeConverter(); - options.AddPropertyConverter(entity => entity.Header, new DataPropertyConverterAttribute() - { - Offset = 0, - Length = 5 - }); - options.AddPropertyConverter(entity => entity.Body, new DataPropertyConverterAttribute() - { - 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(new FixLengthDataPackageHandler(7)); - - OptionConvertEntity? entity = null; - client.AddDataPackageAdapter(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(); - } - - [Fact] - public async Task AddDataPackageAdapter_Ok() - { - var port = 8896; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[128]; - var receivedBuffer2 = new byte[128]; - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack2); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - client.RemoveDataPackageAdapter(ReceivedCallBack); - client.RemoveDataPackageAdapter(ReceivedCallBack2); - - ValueTask ReceivedCallBack(ReadOnlyMemory buffer) - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer); - receivedBuffer = receivedBuffer[..buffer.Length]; - tcs.SetResult(); - return ValueTask.CompletedTask; - } - - ValueTask ReceivedCallBack2(ReadOnlyMemory buffer) - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer2); - receivedBuffer2 = receivedBuffer2[..buffer.Length]; - return ValueTask.CompletedTask; - } - } - - [Fact] - public async Task SetDataPackageAdapter_Ok() - { - var port = 8897; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[128]; - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedCallBack); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - - ValueTask ReceivedCallBack(ReadOnlyMemory buffer) - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer); - receivedBuffer = receivedBuffer[..buffer.Length]; - tcs.SetResult(); - return ValueTask.CompletedTask; - } - } - - [Fact] - public async Task SetDataPackageAdapter_Generic() - { - var port = 8898; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[128]; - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedEntityCallBack); - - client.AddDataPackageAdapter(new DataPackageAdapter(new FixLengthDataPackageHandler(7)), ReceivedCallBack); - client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), new MockSocketDataConverter(), ReceivedEntityCallBack); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - - ValueTask ReceivedCallBack(ReadOnlyMemory buffer) - { - // buffer 即是接收到的数据 - buffer.CopyTo(receivedBuffer); - receivedBuffer = receivedBuffer[..buffer.Length]; - tcs.SetResult(); - return ValueTask.CompletedTask; - } - - Task ReceivedEntityCallBack(MockEntity? entity) - { - tcs.SetResult(); - return Task.CompletedTask; - } - } - - [Fact] - public async Task Convert_Ok() - { - var port = 8899; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - var receivedBuffer = new byte[128]; - MockConverterEntity? entity = null; - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - client.AddDataPackageAdapter(new FixLengthDataPackageHandler(7), ReceivedCallBack); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - - // 验证实体类不为空 - Assert.NotNull(entity); - Assert.Equal("3.14", entity.Value1.ToString("#.##")); - - Task ReceivedCallBack(MockConverterEntity? data) - { - entity = data; - tcs.SetResult(); - return Task.CompletedTask; - } - } - - [DataTypeConverter(Type = typeof(DataConverter))] - class MockConverterEntity - { - [DataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] - public byte[]? Header { get; set; } - - [DataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] - public byte[]? Body { get; set; } - - [DataPropertyConverter(Type = typeof(float), Offset = 5, Length = 1, ConverterType = typeof(FloatConverter), ConverterParameters = [0.01f])] - public float Value1 { get; set; } - } - - class FloatConverter(float rate) : IDataPropertyConverter - { - public object? Convert(ReadOnlyMemory data) - { - return (float)Math.Round(314 * rate, 2); - } - } - - private static TcpListener StartTcpServer(int port, Func handler) - { - var server = new TcpListener(IPAddress.Loopback, port); - server.Start(); - Task.Run(() => AcceptClientsAsync(server, handler)); - return server; - } - - private static async Task AcceptClientsAsync(TcpListener server, Func handler) - { - while (true) - { - var client = await server.AcceptTcpClientAsync(); - _ = Task.Run(() => handler(client)); - } - } - - private static async Task MockDelimiterPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[10240]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - var block = new ReadOnlyMemory(buffer, 0, len); - await stream.WriteAsync(block, CancellationToken.None); - - await Task.Delay(20); - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 13, 10, 0x5, 0x6, 13, 10 }, CancellationToken.None); - } - } - - private static async Task MockSplitPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[1024]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - var block = new ReadOnlyMemory(buffer, 0, len); - await stream.WriteAsync(block, CancellationToken.None); - - // 模拟延时 - await Task.Delay(50); - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4 }, CancellationToken.None); - } - } - - private static async Task MockEntityPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[1024]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - await stream.WriteAsync(new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x3, 0x4, 0x31, 0x09, 0x10, 0x40, 0x09, 0x1E, 0xB8, 0x51, 0xEB, 0x85, 0x1F, 0x40, 0x49, 0x0F, 0xDB, 0x23, 0x24, 0x25, 0x26, 0x01, 0x01, 0x29 }, CancellationToken.None); - } - } - - private static async Task MockStickyPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[10240]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - var block = new ReadOnlyMemory(buffer, 0, len); - await stream.WriteAsync(block, CancellationToken.None); - - // 模拟延时 - await Task.Delay(10); - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4, 0x2, 0x2 }, CancellationToken.None); - - // 模拟延时 - await Task.Delay(10); - - // 模拟粘包发送后续数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4, 0x5, 0x6, 0x7, 0x3, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x1 }, CancellationToken.None); - } - } - - private static async Task LoopSendPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - // 模拟发送数据 - var data = new byte[] { 1, 2, 3, 4, 5 }; - await stream.WriteAsync(data, CancellationToken.None); - // 模拟延时 - await Task.Delay(500); - } - } - - private static void StopTcpServer(TcpListener server) - { - server?.Stop(); - } - - private static ITcpSocketClient CreateClient(Action? builder = null, Action? optionConfigure = null) - { - var sc = new ServiceCollection(); - sc.AddLogging(builder => - { - builder.AddProvider(new MockLoggerProvider()); - }); - sc.AddBootstrapBlazorTcpSocketFactory(); - builder?.Invoke(sc); - - var provider = sc.BuildServiceProvider(); - var factory = provider.GetRequiredService(); - var client = factory.GetOrCreate("test", op => - { - op.LocalEndPoint = TcpSocketUtility.ConvertToIpEndPoint("localhost", 0); - op.EnableLog = true; - optionConfigure?.Invoke(op); - }); - return client; - } - - 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) - { - - } - } - - class MockSendErrorSocketProvider : ITcpSocketClientProvider - { - public bool IsConnected { get; private set; } - - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); - - public ValueTask CloseAsync() - { - return ValueTask.CompletedTask; - } - - public ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - IsConnected = true; - return ValueTask.FromResult(true); - } - - public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - return ValueTask.FromResult(0); - } - - public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - throw new Exception("Mock send error"); - } - } - - class MockConnectTimeoutSocketProvider : ITcpSocketClientProvider - { - public bool IsConnected { get; private set; } - - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); - - public ValueTask CloseAsync() - { - return ValueTask.CompletedTask; - } - - public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - await Task.Delay(1000, token); - IsConnected = false; - return false; - } - - public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - return ValueTask.FromResult(0); - } - - public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - return ValueTask.FromResult(true); - } - } - - class MockConnectCancelSocketProvider : ITcpSocketClientProvider - { - public bool IsConnected { get; private set; } - - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); - - public ValueTask CloseAsync() - { - return ValueTask.CompletedTask; - } - - public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - await Task.Delay(250, token); - return false; - } - - public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - return ValueTask.FromResult(0); - } - - public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - return ValueTask.FromResult(true); - } - } - - class MockSendTimeoutSocketProvider : ITcpSocketClientProvider - { - public bool IsConnected { get; private set; } - - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); - - public ValueTask CloseAsync() - { - return ValueTask.CompletedTask; - } - - public ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - IsConnected = true; - return ValueTask.FromResult(true); - } - - public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - return ValueTask.FromResult(0); - } - - public async ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - // 模拟超时发送 - await Task.Delay(100, token); - return false; - } - } - - class MockAutoReconnectLockSocketProvider : ITcpSocketClientProvider - { - public bool IsConnected { get; private set; } - - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Loopback, 0); - - public async ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - await Task.Delay(100, token); - return IsConnected; - } - - public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - return ValueTask.FromResult(true); - } - - public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - byte[] data = [1, 2, 3, 4, 5]; - data.CopyTo(buffer); - return ValueTask.FromResult(5); - } - - public ValueTask CloseAsync() - { - return ValueTask.CompletedTask; - } - - public void SetConnected(bool state) - { - IsConnected = state; - } - } - - class MockAutoReconnectSocketProvider : ITcpSocketClientProvider - { - public bool IsConnected { get; private set; } - - public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Loopback, 0); - - public ValueTask ConnectAsync(IPEndPoint endPoint, CancellationToken token = default) - { - return ValueTask.FromResult(IsConnected); - } - - private bool _sendState = true; - public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - return ValueTask.FromResult(_sendState); - } - - private bool _receiveState = true; - public ValueTask ReceiveAsync(Memory buffer, CancellationToken token = default) - { - if (_receiveState) - { - byte[] data = [1, 2, 3, 4, 5]; - data.CopyTo(buffer); - return ValueTask.FromResult(5); - } - else - { - return ValueTask.FromResult(0); - } - } - - public ValueTask CloseAsync() - { - return ValueTask.CompletedTask; - } - - public void SetConnected(bool state) - { - IsConnected = state; - } - - public void SetSend(bool state) - { - _sendState = state; - } - - public void SetReceive(bool state) - { - _receiveState = state; - } - } - - [DataTypeConverter(Type = typeof(DataConverter))] - class MockEntity - { - [DataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] - public byte[]? Header { get; set; } - - [DataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] - public byte[]? Body { get; set; } - - [DataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] - public string? Value1 { get; set; } - - [DataPropertyConverter(Type = typeof(int), Offset = 8, Length = 1)] - public int Value2 { get; set; } - - [DataPropertyConverter(Type = typeof(long), Offset = 9, Length = 1)] - public long Value3 { get; set; } - - [DataPropertyConverter(Type = typeof(double), Offset = 10, Length = 8)] - public double Value4 { get; set; } - - [DataPropertyConverter(Type = typeof(float), Offset = 18, Length = 4)] - public float Value5 { get; set; } - - [DataPropertyConverter(Type = typeof(short), Offset = 22, Length = 1)] - public short Value6 { get; set; } - - [DataPropertyConverter(Type = typeof(ushort), Offset = 23, Length = 1)] - public ushort Value7 { get; set; } - - [DataPropertyConverter(Type = typeof(uint), Offset = 24, Length = 1)] - public uint Value8 { get; set; } - - [DataPropertyConverter(Type = typeof(ulong), Offset = 25, Length = 1)] - public ulong Value9 { get; set; } - - [DataPropertyConverter(Type = typeof(bool), Offset = 26, Length = 1)] - public bool Value10 { get; set; } - - [DataPropertyConverter(Type = typeof(EnumEducation), Offset = 27, Length = 1)] - public EnumEducation Value11 { get; set; } - - [DataPropertyConverter(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] - public Foo? Value12 { get; set; } - - [DataPropertyConverter(Type = typeof(string), Offset = 7, Length = 1)] - public string? Value14 { get; set; } - - public string? Value13 { get; set; } - - [DataPropertyConverter(Type = typeof(byte), Offset = 0, Length = 1)] - public byte Value15 { get; set; } - - [DataPropertyConverter(Type = typeof(byte), ConverterType = typeof(MockNullConverter), Offset = 0, Length = 1)] - public byte Value16 { get; set; } - - [DataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 1)] - public byte Value17 { get; set; } - } - - class MockSocketDataConverter : DataConverter - { - protected override bool Parse(ReadOnlyMemory data, MockEntity entity) - { - return false; - } - } - - class MockErrorDataConverter : DataConverter - { - protected override bool Parse(ReadOnlyMemory data, MockEntity entity) - { - throw new Exception("Mock parse error"); - } - } - - class MockNullConverter : IDataPropertyConverter - { - public object? Convert(ReadOnlyMemory data) - { - return null; - } - } - - class FooConverter(string name) : IDataPropertyConverter - { - public object? Convert(ReadOnlyMemory data) - { - return new Foo() { Id = data.Span[0], Name = name }; - } - } - - class NoConvertEntity - { - public byte[]? Header { get; set; } - - public byte[]? Body { get; set; } - } - - class OptionConvertEntity - { - public byte[]? Header { get; set; } - - public byte[]? Body { get; set; } - } -} diff --git a/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs b/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs deleted file mode 100644 index 09dfc439..00000000 --- a/test/UnitTestTcpSocket/TcpSocketPropertyConverterTest.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -namespace UnitTestTcpSocket; - -public class TcpSocketPropertyConverterTest -{ - [Fact] - public void UInt16Converter_Ok() - { - var converter = new DataUInt16LittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); - Assert.Equal((ushort)0xFF, actual); - } - - [Fact] - public void Int16Converter_Ok() - { - var converter = new DataInt16LittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); - Assert.Equal((short)0xFF, actual); - } - - [Fact] - public void UInt32Converter_Ok() - { - var converter = new DataUInt32LittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); - Assert.Equal((uint)0xFF, actual); - } - - [Fact] - public void Int32Converter_Ok() - { - var converter = new DataInt32LittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); - Assert.Equal(0xFF, actual); - } - - [Fact] - public void UInt64Converter_Ok() - { - var converter = new DataUInt64LittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - Assert.Equal((ulong)0xFF, actual); - } - - [Fact] - public void Int64Converter_Ok() - { - var converter = new DataInt64LittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - Assert.Equal((long)0xFF, actual); - } - - [Fact] - public void SingleConverter_Ok() - { - var converter = new DataSingleLittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }); - Assert.Equal((float)3.14, actual); - } - - [Fact] - public void DoubleConverter_Ok() - { - var converter = new DataDoubleLittleEndianConverter(); - var actual = converter.Convert(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }); - Assert.Equal(3.14, actual); - } - - [Fact] - public void ByteConverter_Ok() - { - var converter = new DataByteConverter(); - var actual = converter.Convert(new byte[] { 0xFF }); - Assert.Equal((byte)0xFF, actual); - - actual = converter.Convert(Array.Empty()); - Assert.Equal((byte)0x0, actual); - } -} diff --git a/test/UnitTestTcpSocket/TcpSocketUtiityTest.cs b/test/UnitTestTcpSocket/TcpSocketUtiityTest.cs deleted file mode 100644 index 1f259696..00000000 --- a/test/UnitTestTcpSocket/TcpSocketUtiityTest.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using System.Net; - -namespace UnitTestTcpSocket; - -public class TcpSocketUtiityTest -{ - - [Fact] - public void ConvertToIPAddress_Ok() - { - var ex = Assert.Throws(() => TcpSocketUtility.ConvertToIPAddress("")); - Assert.NotNull(ex); - - var address = TcpSocketUtility.ConvertToIPAddress("any"); - Assert.Equal(IPAddress.Any, address); - } - - [Fact] - public void ConvertToIpEndPoint_Ok() - { - var ex = Assert.Throws(() => TcpSocketUtility.ConvertToIpEndPoint("localhost", 88990)); - Assert.NotNull(ex); - - ex = null; - ex = Assert.Throws(() => TcpSocketUtility.ConvertToIpEndPoint("localhost", -1000)); - Assert.NotNull(ex); - } -} diff --git a/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj b/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj deleted file mode 100644 index fdb9ea2c..00000000 --- a/test/UnitTestTcpSocket/UnitTestTcpSocket.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - - diff --git a/test/UnitTestTouchSocket/TcpSocketFactoryTest.cs b/test/UnitTestTouchSocket/TcpSocketFactoryTest.cs deleted file mode 100644 index 2bc39c0a..00000000 --- a/test/UnitTestTouchSocket/TcpSocketFactoryTest.cs +++ /dev/null @@ -1,695 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using Microsoft.Extensions.Logging; -using System.Net; -using System.Net.Sockets; -using System.Reflection; -using System.Text; - -namespace UnitTest.Services; - -public class TcpSocketFactoryTest -{ - [Fact] - public async Task GetOrCreate_Ok() - { - // 测试 GetOrCreate 方法创建的 Client 销毁后继续 GetOrCreate 得到的对象是否可用 - var sc = new ServiceCollection(); - sc.AddLogging(builder => - { - builder.AddProvider(new MockLoggerProvider()); - }); - sc.AddBootstrapBlazorTcpSocketFactory(); - sc.AddBootstrapBlazorTouchSocketProviderService(); - - var provider = sc.BuildServiceProvider(); - var factory = provider.GetRequiredService(); - var client1 = factory.GetOrCreate("demo", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); - await client1.CloseAsync(); - - var client2 = factory.GetOrCreate("demo", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); - Assert.Equal(client1, client2); - - var ip = Dns.GetHostAddresses(Dns.GetHostName(), AddressFamily.InterNetwork).FirstOrDefault() ?? IPAddress.Loopback; - var client3 = factory.GetOrCreate("demo1", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint(ip.ToString(), 0)); - - // 测试不合格 IP 地址 - var client4 = factory.GetOrCreate("demo2", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("256.0.0.1", 0)); - - var client5 = factory.Remove("demo2"); - Assert.Equal(client4, client5); - Assert.NotNull(client5); - - await client5.DisposeAsync(); - await factory.DisposeAsync(); - } - - [Fact] - public async Task ConnectAsync_Timeout() - { - var client = CreateClient(); - client.Options.ConnectTimeout = 100; - - var connect = await client.ConnectAsync("localhost", 9999); - Assert.False(connect); - } - - [Fact] - public async Task ConnectAsync_Cancel() - { - var client = CreateClient(); - - // 测试 ConnectAsync 方法连接取消逻辑 - var cst = new CancellationTokenSource(); - cst.Cancel(); - var connect = await client.ConnectAsync("localhost", 9999, cst.Token); - Assert.False(connect); - } - - [Fact] - public async Task ConnectAsync_Failed() - { - var client = CreateClient(); - - // 测试 ConnectAsync 方法连接失败 - var connect = await client.ConnectAsync("localhost", 9999); - Assert.False(connect); - } - - [Fact] - public async Task ConnectAsync_Error() - { - var client = CreateClient(); - - // 反射设置 SocketClientProvider 为空 - var propertyInfo = client.GetType().GetProperty("ServiceProvider", BindingFlags.Public | BindingFlags.Instance); - Assert.NotNull(propertyInfo); - propertyInfo.SetValue(client, null); - - // 测试 ConnectAsync 方法连接失败 - var ex = await Assert.ThrowsAsync(async () => await client.ConnectAsync("localhost", 9999)); - Assert.NotNull(ex); - - // 反射测试 Log 方法 - var methodInfo = client.GetType().GetMethod("Log", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(methodInfo); - methodInfo.Invoke(client, [LogLevel.Error, null!, "Test error log"]); - } - - [Fact] - public async Task Send_Timeout() - { - var port = 8887; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - client.Options.SendTimeout = 100; - client.SetDataHandler(new MockSendTimeoutHandler()); - - await client.ConnectAsync("localhost", port); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var result = await client.SendAsync(data); - Assert.False(result); - } - - [Fact] - public async Task SendAsync_Error() - { - var client = CreateClient(); - - // 测试未建立连接前调用 SendAsync 方法报异常逻辑 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var ex = await Assert.ThrowsAsync(async () => await client.SendAsync(data)); - Assert.NotNull(ex); - - // 测试发送失败 - var port = 8892; - var server = StartTcpServer(port, MockSplitPackageAsync); - - client.SetDataHandler(new MockSendErrorHandler()); - await client.ConnectAsync("localhost", port); - Assert.True(client.IsConnected); - - // 内部生成异常日志 - await client.SendAsync(data); - } - - [Fact] - public async Task SendAsync_Cancel() - { - var port = 8881; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - Assert.False(client.IsConnected); - - // 连接 TCP Server - await client.ConnectAsync("localhost", port); - Assert.True(client.IsConnected); - - // 测试 SendAsync 方法发送取消逻辑 - var cst = new CancellationTokenSource(); - cst.Cancel(); - - var result = await client.SendAsync("test", null, cst.Token); - Assert.False(result); - - // 设置延时发送适配器 - // 延时发送期间关闭 Socket 连接导致内部报错 - client.SetDataHandler(new MockSendCancelHandler() - { - Socket = client - }); - - var tcs = new TaskCompletionSource(); - bool? sendResult = null; - // 测试发送失败逻辑 - _ = Task.Run(async () => - { - sendResult = await client.SendAsync("test", Encoding.UTF8); - tcs.SetResult(); - }); - - await tcs.Task; - Assert.False(sendResult); - - // 关闭连接 - StopTcpServer(server); - } - - [Fact] - public async Task ReceiveAsync_Timeout() - { - var port = 8888; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - client.Options.ReceiveTimeout = 100; - client.SetDataHandler(new MockReceiveTimeoutHandler()); - - await client.ConnectAsync("localhost", port); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - await Task.Delay(220); // 等待接收超时 - } - - [Fact] - public async Task ReceiveAsync_Cancel() - { - var port = 8889; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - client.SetDataHandler(new MockReceiveTimeoutHandler()); - - await client.ConnectAsync("localhost", port); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 通过反射取消令牌 - var baseType = client.GetType().BaseType; - Assert.NotNull(baseType); - - var fieldInfo = baseType.GetField("_receiveCancellationTokenSource", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(fieldInfo); - var tokenSource = fieldInfo.GetValue(client) as CancellationTokenSource; - Assert.NotNull(tokenSource); - tokenSource.Cancel(); - await Task.Delay(50); - } - - [Fact] - public async Task ReceiveAsync_InvalidOperationException() - { - // 未连接时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常 - var client = CreateClient(); - var ex = await Assert.ThrowsAsync(async () => await client.ReceiveAsync()); - Assert.NotNull(ex); - - // 已连接但是启用了自动接收功能时调用 ReceiveAsync 方法会抛出 InvalidOperationException 异常 - var port = 8893; - var server = StartTcpServer(port, MockSplitPackageAsync); - - client.Options.IsAutoReceive = true; - var connected = await client.ConnectAsync("localhost", port); - Assert.True(connected); - - ex = await Assert.ThrowsAsync(async () => await client.ReceiveAsync()); - Assert.NotNull(ex); - } - - [Fact] - public async Task ReceiveAsync_Ok() - { - var port = 8891; - var server = StartTcpServer(port, MockSplitPackageAsync); - - var client = CreateClient(); - client.Options.IsAutoReceive = false; - var connected = await client.ConnectAsync("localhost", port); - Assert.True(connected); - - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var send = await client.SendAsync(data); - Assert.True(send); - - var payload = await client.ReceiveAsync(); - Assert.Equal(payload.ToArray(), [1, 2, 3, 4, 5]); - } - - [Fact] - public async Task ReceiveAsync_Error() - { - var client = CreateClient(); - - // 测试未建立连接前调用 ReceiveAsync 方法报异常逻辑 - var baseType = client.GetType().BaseType; - Assert.NotNull(baseType); - - var methodInfo = baseType.GetMethod("AutoReceiveAsync", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(methodInfo); - - var task = (ValueTask)methodInfo.Invoke(client, null)!; - var ex = await Assert.ThrowsAsync(async () => await task); - Assert.NotNull(ex); - - var port = 8882; - var server = StartTcpServer(port, MockSplitPackageAsync); - - Assert.Equal(1024 * 64, client.Options.ReceiveBufferSize); - - client.Options.ReceiveBufferSize = 1024 * 20; - Assert.Equal(1024 * 20, client.Options.ReceiveBufferSize); - - client.SetDataHandler(new MockReceiveErrorHandler()); - - ReadOnlyMemory buffer = ReadOnlyMemory.Empty; - var tcs = new TaskCompletionSource(); - - // 增加接收回调方法 - client.ReceivedCallBack = b => - { - buffer = b; - tcs.SetResult(); - return ValueTask.CompletedTask; - }; - - await client.ConnectAsync("localhost", port); - - // 发送数据导致接收数据异常 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - await tcs.Task; - Assert.Equal(buffer.ToArray(), [1, 2, 3, 4, 5]); - - // 关闭连接 - StopTcpServer(server); - } - - [Fact] - public async Task CloseByRemote_Ok() - { - var client = CreateClient(); - - var port = 8883; - var server = StartTcpServer(port, MockAutoClosePackageAsync); - - client.SetDataHandler(new MockReceiveErrorHandler()); - - // 连接 TCP Server - await client.ConnectAsync("localhost", port); - - // 发送数据 - await client.SendAsync(new ReadOnlyMemory([1, 2, 3, 4, 5])); - - // 关闭连接 - StopTcpServer(server); - } - - [Fact] - public async Task FixLengthDataPackageHandler_Ok() - { - var port = 8884; - var server = StartTcpServer(port, MockSplitPackageAsync); - var client = CreateClient(); - - // 测试 ConnectAsync 方法 - var connect = await client.ConnectAsync("localhost", port); - Assert.True(connect); - Assert.True(client.IsConnected); - - var tcs = new TaskCompletionSource(); - ReadOnlyMemory receivedBuffer = ReadOnlyMemory.Empty; - - // 增加数据处理适配器 - client.SetDataHandler(new FixLengthDataPackageHandler(7) - { - ReceivedCallBack = buffer => - { - receivedBuffer = buffer; - tcs.SetResult(); - return ValueTask.CompletedTask; - } - }); - - // 测试 SendAsync 方法 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - var result = await client.SendAsync(data); - Assert.True(result); - - await tcs.Task; - Assert.Equal(receivedBuffer.ToArray(), [1, 2, 3, 4, 5, 3, 4]); - - // 模拟延时等待内部继续读取逻辑完成,测试内部 _receiveCancellationTokenSource 取消逻辑 - await Task.Delay(10); - - // 关闭连接 - await client.CloseAsync(); - StopTcpServer(server); - } - - [Fact] - public async Task FixLengthDataPackageHandler_Sticky() - { - var port = 8885; - var server = StartTcpServer(port, MockStickyPackageAsync); - var client = CreateClient(); - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - var tcs = new TaskCompletionSource(); - ReadOnlyMemory receivedBuffer = ReadOnlyMemory.Empty; - - // 增加数据库处理适配器 - client.SetDataHandler(new FixLengthDataPackageHandler(7) - { - ReceivedCallBack = buffer => - { - receivedBuffer = buffer; - tcs.SetResult(); - return ValueTask.CompletedTask; - } - }); - - // 发送数据 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - - // 验证接收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [1, 2, 3, 4, 5, 3, 4]); - receivedBuffer = ReadOnlyMemory.Empty; - tcs = new TaskCompletionSource(); - - // 等待第二次数据 - await tcs.Task; - - // 验证第二次收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [2, 2, 3, 4, 5, 6, 7]); - tcs = new TaskCompletionSource(); - await tcs.Task; - - // 验证第三次收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [3, 2, 3, 4, 5, 6, 7]); - - // 关闭连接 - await client.CloseAsync(); - StopTcpServer(server); - } - - [Fact] - public async Task DelimiterDataPackageHandler_Ok() - { - var port = 8886; - var server = StartTcpServer(port, MockDelimiterPackageAsync); - var client = CreateClient(); - - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); - - var tcs = new TaskCompletionSource(); - ReadOnlyMemory receivedBuffer = ReadOnlyMemory.Empty; - - // 增加数据库处理适配器 - client.SetDataHandler(new DelimiterDataPackageHandler([0x13, 0x10]) - { - ReceivedCallBack = buffer => - { - receivedBuffer = buffer; - tcs.SetResult(); - return ValueTask.CompletedTask; - } - }); - - // 发送数据 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); - await client.SendAsync(data); - - // 等待接收数据处理完成 - await tcs.Task; - - // 验证接收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [1, 2, 3, 4, 5, 0x13, 0x10]); - - // 等待第二次数据 - receivedBuffer = ReadOnlyMemory.Empty; - tcs = new TaskCompletionSource(); - await tcs.Task; - - // 验证接收到的数据 - Assert.Equal(receivedBuffer.ToArray(), [5, 6, 0x13, 0x10]); - - // 关闭连接 - await client.CloseAsync(); - StopTcpServer(server); - - var handler = new DelimiterDataPackageHandler("\r\n"); - var ex = Assert.Throws(() => new DelimiterDataPackageHandler(string.Empty)); - Assert.NotNull(ex); - - ex = Assert.Throws(() => new DelimiterDataPackageHandler((byte[])null!)); - Assert.NotNull(ex); - } - - private static TcpListener StartTcpServer(int port, Func handler) - { - var server = new TcpListener(IPAddress.Loopback, port); - server.Start(); - Task.Run(() => AcceptClientsAsync(server, handler)); - return server; - } - - private static async Task AcceptClientsAsync(TcpListener server, Func handler) - { - while (true) - { - var client = await server.AcceptTcpClientAsync(); - _ = Task.Run(() => handler(client)); - } - } - - private static async Task MockDelimiterPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[10240]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - var block = new ReadOnlyMemory(buffer, 0, len); - await stream.WriteAsync(block, CancellationToken.None); - - await Task.Delay(20); - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 0x13, 0x10, 0x5, 0x6, 0x13, 0x10 }, CancellationToken.None); - } - } - - private static async Task MockSplitPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[1024]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - var block = new ReadOnlyMemory(buffer, 0, len); - await stream.WriteAsync(block, CancellationToken.None); - - // 模拟延时 - await Task.Delay(50); - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4 }, CancellationToken.None); - } - } - - private static async Task MockStickyPackageAsync(TcpClient client) - { - using var stream = client.GetStream(); - while (true) - { - var buffer = new byte[10240]; - var len = await stream.ReadAsync(buffer); - if (len == 0) - { - break; - } - - // 回写数据到客户端 - var block = new ReadOnlyMemory(buffer, 0, len); - await stream.WriteAsync(block, CancellationToken.None); - - // 模拟延时 - await Task.Delay(10); - - // 模拟拆包发送第二段数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4, 0x2, 0x2 }, CancellationToken.None); - - // 模拟延时 - await Task.Delay(10); - - // 模拟粘包发送后续数据 - await stream.WriteAsync(new byte[] { 0x3, 0x4, 0x5, 0x6, 0x7, 0x3, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x1 }, CancellationToken.None); - } - } - - private static Task MockAutoClosePackageAsync(TcpClient client) - { - client.Close(); - return Task.CompletedTask; - } - - private static void StopTcpServer(TcpListener server) - { - server?.Stop(); - } - - private static ITcpSocketClient CreateClient() - { - var sc = new ServiceCollection(); - sc.AddLogging(builder => - { - builder.AddProvider(new MockLoggerProvider()); - }); - sc.AddBootstrapBlazorTcpSocketFactory(); - sc.AddBootstrapBlazorTouchSocketProviderService(); - - var provider = sc.BuildServiceProvider(); - var factory = provider.GetRequiredService(); - var client = factory.GetOrCreate("test", op => op.LocalEndPoint = Utility.ConvertToIpEndPoint("localhost", 0)); - return client; - } - - 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) - { - - } - } - - class MockSendErrorHandler : DataPackageHandlerBase - { - public ITcpSocketClient? Socket { get; set; } - - public override ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - throw new Exception("Mock send failed"); - } - } - - class MockSendCancelHandler : DataPackageHandlerBase - { - public ITcpSocketClient? Socket { get; set; } - - public override async ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - if (Socket != null) - { - await Socket.CloseAsync(); - } - await Task.Delay(10, token); - return data; - } - } - - class MockReceiveErrorHandler : DataPackageHandlerBase - { - public override ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - return ValueTask.FromResult(data); - } - - public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) - { - await base.ReceiveAsync(data, token); - - // 模拟接收数据时报错 - throw new InvalidOperationException("Test Error"); - } - } - - class MockSendTimeoutHandler : DataPackageHandlerBase - { - public override async ValueTask> SendAsync(ReadOnlyMemory data, CancellationToken token = default) - { - // 模拟发送超时 - await Task.Delay(200, token); - return data; - } - } - - class MockReceiveTimeoutHandler : DataPackageHandlerBase - { - public override async ValueTask ReceiveAsync(ReadOnlyMemory data, CancellationToken token = default) - { - // 模拟接收超时 - await Task.Delay(200, token); - await base.ReceiveAsync(data, token); - } - } -} diff --git a/test/UnitTestTouchSocket/UnitTestTouchSocket.csproj b/test/UnitTestTouchSocket/UnitTestTouchSocket.csproj deleted file mode 100644 index 91bee258..00000000 --- a/test/UnitTestTouchSocket/UnitTestTouchSocket.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - From 18ece790caedb2dcba0e3650bbde6624103d2e4b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 3 Sep 2025 14:05:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=20sln=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BootstrapBlazor.Extensions.sln | 655 --------------------------------- 1 file changed, 655 deletions(-) delete mode 100644 BootstrapBlazor.Extensions.sln diff --git a/BootstrapBlazor.Extensions.sln b/BootstrapBlazor.Extensions.sln deleted file mode 100644 index c5d4f29b..00000000 --- a/BootstrapBlazor.Extensions.sln +++ /dev/null @@ -1,655 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.0.10828.68 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{FF1089BE-C704-4374-B629-C57C08E1798F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.BarcodeGenerator", "src\components\BootstrapBlazor.BarcodeGenerator\BootstrapBlazor.BarcodeGenerator.csproj", "{0E6F09A3-61C4-4945-9DC8-5E00CE547DBD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.FileSystem", "src\components\BootstrapBlazor.FileSystem\BootstrapBlazor.FileSystem.csproj", "{203B9DE4-201C-4223-AD0E-4823777AB9A7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.FileViewer", "src\components\BootstrapBlazor.FileViewer\BootstrapBlazor.FileViewer.csproj", "{3EBD90E6-5237-4BB0-9C14-CC1FC5956A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.ImageCropper", "src\components\BootstrapBlazor.ImageCropper\BootstrapBlazor.ImageCropper.csproj", "{079E4289-E2C6-47A9-98C2-9193F8A5D415}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.ImageHelper", "src\components\BootstrapBlazor.ImageHelper\BootstrapBlazor.ImageHelper.csproj", "{BF6F0A50-93DA-4F40-9936-933FEBF3B5CA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Maps", "src\components\BootstrapBlazor.Maps\BootstrapBlazor.Maps.csproj", "{A6FE7E0D-04EB-4E74-9C28-0B0920BEF7D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.MindMap", "src\components\BootstrapBlazor.MindMap\BootstrapBlazor.MindMap.csproj", "{72D0DB26-0437-499F-ACD6-4F90EA8513ED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.OCR", "src\components\BootstrapBlazor.OCR\BootstrapBlazor.OCR.csproj", "{F979AFA9-E3CF-43DF-A7EA-3C63213D3C72}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.OfdReader", "src\components\BootstrapBlazor.OfdReader\BootstrapBlazor.OfdReader.csproj", "{C1BD1FA6-BFD5-42E3-ADB5-4D4BA51653BA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.OnScreenKeyboard", "src\components\BootstrapBlazor.OnScreenKeyboard\BootstrapBlazor.OnScreenKeyboard.csproj", "{21A163D4-DC81-4B87-BA1B-9B5F48D66587}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.OpenAI", "src\components\BootstrapBlazor.OpenAI.GPT3\BootstrapBlazor.OpenAI.csproj", "{4E88785F-AD6A-4B57-BEC1-89F63A51BB63}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.PdfReader", "src\components\BootstrapBlazor.PdfReader\BootstrapBlazor.PdfReader.csproj", "{D84EF7E9-2A5E-4EA8-BC2B-54289276A16D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.SignaturePad", "src\components\BootstrapBlazor.SignaturePad\BootstrapBlazor.SignaturePad.csproj", "{CC30AFF5-305E-43C8-B230-1A82AE3041F2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.VideoPlayer", "src\components\BootstrapBlazor.VideoPlayer\BootstrapBlazor.VideoPlayer.csproj", "{CE85D4DD-6F4A-49AF-B575-2429FC545E7B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Viewer", "src\components\BootstrapBlazor.Viewer\BootstrapBlazor.Viewer.csproj", "{98BF777B-3AC1-4169-8633-F69D9A6EB275}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.WebAPI", "src\components\BootstrapBlazor.WebAPI\BootstrapBlazor.WebAPI.csproj", "{AB6EE78F-B778-42BA-91AB-234AC4F60C16}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.AntDesignIcon", "src\components\BootstrapBlazor.AntDesignIcon\BootstrapBlazor.AntDesignIcon.csproj", "{7FFCC03C-147B-4257-A035-2A31A807F173}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.AzureOpenAI", "src\components\BootstrapBlazor.AzureOpenAI\BootstrapBlazor.AzureOpenAI.csproj", "{C546E031-1A92-409F-8E9C-4FD9A15FC042}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.AzureSpeech", "src\components\BootstrapBlazor.AzureSpeech\BootstrapBlazor.AzureSpeech.csproj", "{26DEC31C-40C2-4F3B-9A9D-2BEA313AA59C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.AzureTranslator", "src\components\BootstrapBlazor.AzureTranslator\BootstrapBlazor.AzureTranslator.csproj", "{65181D1B-AD7C-4223-B230-6AB95EC1DA19}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.BaiduOcr", "src\components\BootstrapBlazor.BaiduOcr\BootstrapBlazor.BaiduOcr.csproj", "{FBC02566-6622-43DC-B105-A0C95E9D94EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.BaiduSpeech", "src\components\BootstrapBlazor.BaiduSpeech\BootstrapBlazor.BaiduSpeech.csproj", "{14EBAC86-E192-4BFA-80C1-91F91BDDE6A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.BarCode", "src\components\BootstrapBlazor.BarCode\BootstrapBlazor.BarCode.csproj", "{A5F1444E-D86F-4740-BADD-EF042193FD92}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.BootstrapIcon", "src\components\BootstrapBlazor.BootstrapIcon\BootstrapBlazor.BootstrapIcon.csproj", "{C78E90E8-47EC-4A1E-A108-D0CD018CE417}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Chart", "src\components\BootstrapBlazor.Chart\BootstrapBlazor.Chart.csproj", "{A9D480F3-7D56-4E82-A71A-3378DE18A2D2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.CherryMarkdown", "src\components\BootstrapBlazor.CherryMarkdown\BootstrapBlazor.CherryMarkdown.csproj", "{A6B947E9-450C-45D7-BDC0-AE3C12D3FBA3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.CodeEditor", "src\components\BootstrapBlazor.CodeEditor\BootstrapBlazor.CodeEditor.csproj", "{80A0135B-6F5B-4CB0-90F5-0060D241136A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Dock", "src\components\BootstrapBlazor.Dock\BootstrapBlazor.Dock.csproj", "{B0D2BBC7-D1BB-4F7C-ADE0-59BEBF3BB5D0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.DockView", "src\components\BootstrapBlazor.DockView\BootstrapBlazor.DockView.csproj", "{4B71D8F7-CF9C-44CE-B371-45FBFEE0FB6E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.DriverJs", "src\components\BootstrapBlazor.DriverJs\BootstrapBlazor.DriverJs.csproj", "{F538D58D-58B1-4BA2-A933-E91AC168F568}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.ElementIcon", "src\components\BootstrapBlazor.ElementIcon\BootstrapBlazor.ElementIcon.csproj", "{BB03EAB9-9B5F-49E2-87CC-E61A06AF2DE7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.FloatingUI", "src\components\BootstrapBlazor.FloatingUI\BootstrapBlazor.FloatingUI.csproj", "{309D35E1-8B17-4488-93BC-D6B301B1F853}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.FontAwesome", "src\components\BootstrapBlazor.FontAwesome\BootstrapBlazor.FontAwesome.csproj", "{CC7C4278-BC2E-49D0-BE18-BF8D28EFE6E7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Gantt", "src\components\BootstrapBlazor.Gantt\BootstrapBlazor.Gantt.csproj", "{BB7FBAC6-0F98-4443-B3A0-B7D0F4164D20}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Holiday", "src\components\BootstrapBlazor.Holiday\BootstrapBlazor.Holiday.csproj", "{162A2C8E-767C-41A7-9020-729E79C4F1F9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Html2Pdf", "src\components\BootstrapBlazor.Html2Pdf\BootstrapBlazor.Html2Pdf.csproj", "{E386EBD8-0028-42B7-8473-F26014C0E326}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.IconPark", "src\components\BootstrapBlazor.IconPark\BootstrapBlazor.IconPark.csproj", "{8B374E31-BBC3-4765-82C7-E124C4E1E731}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Live2DDisplay", "src\components\BootstrapBlazor.Live2DDisplay\BootstrapBlazor.Live2DDisplay.csproj", "{496554A7-52B0-4E01-9CF3-08F0A2B2FCD8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Markdown", "src\components\BootstrapBlazor.Markdown\BootstrapBlazor.Markdown.csproj", "{7C875492-41A2-401D-90B1-161BCD5103DC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.MaterialDesign", "src\components\BootstrapBlazor.MaterialDesign\BootstrapBlazor.MaterialDesign.csproj", "{126D5C86-2C33-4E79-A762-DBEA6E7A779E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.MouseFollower", "src\components\BootstrapBlazor.MouseFollower\BootstrapBlazor.MouseFollower.csproj", "{7DD55FF1-54BF-4B4C-9B74-603C79D9EC07}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Player", "src\components\BootstrapBlazor.Player\BootstrapBlazor.Player.csproj", "{2D0F834A-522F-4CF3-A6BB-BC2585D8505E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Sortable", "src\components\BootstrapBlazor.Sortable\BootstrapBlazor.Sortable.csproj", "{9F61C73D-EFF4-4226-ADEF-4B32F03AA371}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Splitting", "src\components\BootstrapBlazor.Splitting\BootstrapBlazor.Splitting.csproj", "{4D0A145A-029E-4DD9-841F-302E8B2AB143}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.SummerNote", "src\components\BootstrapBlazor.SummerNote\BootstrapBlazor.SummerNote.csproj", "{150033CC-28E8-47B2-9704-F6203964B5F1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.TableExport", "src\components\BootstrapBlazor.TableExport\BootstrapBlazor.TableExport.csproj", "{AC89886C-4216-4D20-B8DD-CFEAFC6AB2C8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.TagHelper", "src\components\BootstrapBlazor.TagHelper\BootstrapBlazor.TagHelper.csproj", "{45F1D4EB-AFB6-484B-90D3-DB684B76E693}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.Topology", "src\components\BootstrapBlazor.Topology\BootstrapBlazor.Topology.csproj", "{2D702BA7-AB23-46BD-9F54-7F7E28322790}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.WinBox", "src\components\BootstrapBlazor.WinBox\BootstrapBlazor.WinBox.csproj", "{A8113292-7E73-417A-8E23-9F69F5577816}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "configurations", "configurations", "{3A0D5F7E-6701-437A-9EE7-05516FBCEA1B}" - ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - exclusion.dic = exclusion.dic - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.DataAccess.EntityFrameworkCore", "src\extensions\BootstrapBlazor.DataAccess.EntityFrameworkCore\BootstrapBlazor.DataAccess.EntityFrameworkCore.csproj", "{2662E3C2-F511-4DE0-AA72-FE453031993F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.DataAccess.FreeSql", "src\extensions\BootstrapBlazor.DataAccess.FreeSql\BootstrapBlazor.DataAccess.FreeSql.csproj", "{9BFFB509-9B57-4FE7-B2A1-4D6AFEEE0067}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.DataAccess.PetaPoco", "src\extensions\BootstrapBlazor.DataAccess.PetaPoco\BootstrapBlazor.DataAccess.PetaPoco.csproj", "{2E338270-C302-4E13-A681-7F80E1BEE158}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapBlazor.DataAccess.SqlSugar", "src\extensions\BootstrapBlazor.DataAccess.SqlSugar\BootstrapBlazor.DataAccess.SqlSugar.csproj", "{750E2849-EA95-4849-A7E7-09920E30765D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "middelware", "middelware", "{BB06AD5B-3F2B-4B59-A135-36AF999A614F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestEditor", "test\UnitTestEditor\UnitTestEditor.csproj", "{37979C83-5E5F-4525-85D7-DA20B27F831F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestHoliday", "test\UnitTestHoliday\UnitTestHoliday.csproj", "{22594AD2-3E36-4984-949C-8B578B402EDE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A48E5EAB-6EFB-4A78-810E-A3E79CF749F5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "props", "props", "{FF8B3A19-60DC-4802-9FA0-9FE670C7D565}" - ProjectSection(SolutionItems) = preProject - src\.gitignore = src\.gitignore - src\Directory.Build.props = src\Directory.Build.props - src\Directory.Build.targets = src\Directory.Build.targets - src\Frameworks.props = src\Frameworks.props - src\Logo.props = src\Logo.props - src\readme.md = src\readme.md - src\SourceLink.targets = src\SourceLink.targets - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Middleware", "src\middleware\BootstrapBlazor.Middleware\BootstrapBlazor.Middleware.csproj", "{2D783293-CEBD-4365-B8A1-0F9E76D73295}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Html2Pdf.Select", "src\components\BootstrapBlazor.Html2Pdf.Select\BootstrapBlazor.Html2Pdf.Select.csproj", "{11FD9DF2-3F3F-4AF1-8C85-446190F7D887}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.SvgEditor", "src\components\BootstrapBlazor.SVGEditor\BootstrapBlazor.SvgEditor.csproj", "{29E47F4A-CE03-42B5-BDAA-FB4B40D4C897}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Mermaid", "src\components\BootstrapBlazor.Mermaid\BootstrapBlazor.Mermaid.csproj", "{DA2198E1-2CA9-EE53-926B-7950AB4B5EBF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.MeiliSearch", "src\components\BootstrapBlazor.MeiliSearch\BootstrapBlazor.MeiliSearch.csproj", "{4B086A62-5F5A-47BC-921F-35803F26DD68}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.RDKit", "src\components\BootstrapBlazor.RDKit\BootstrapBlazor.RDKit.csproj", "{7328E464-AE3C-4277-BEC3-422C56637066}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.SmilesDrawer", "src\components\BootstrapBlazor.SmilesDrawer\BootstrapBlazor.SmilesDrawer.csproj", "{84823875-1B07-4CCE-A009-29AEF90C6C10}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OctIcon", "src\components\BootstrapBlazor.OctIcon\BootstrapBlazor.OctIcon.csproj", "{AA4EDA37-1D81-4235-A7F6-F1C112B364EF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.IP2Region", "src\components\BootstrapBlazor.IP2Region\BootstrapBlazor.IP2Region.csproj", "{8A46FEDF-FD76-48AB-9BB2-47254C7623A4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.JuHeIpLocatorProvider", "src\components\BootstrapBlazor.JuHeIpLocatorProvider\BootstrapBlazor.JuHeIpLocatorProvider.csproj", "{C882891F-4A6A-C9BE-AC96-227764A4A46A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Html2Image", "src\components\BootstrapBlazor.Html2Image\BootstrapBlazor.Html2Image.csproj", "{F184D96E-7855-4E3B-B447-D09DBC1C91C6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.UniverSheet", "src\components\BootstrapBlazor.UniverSheet\BootstrapBlazor.UniverSheet.csproj", "{E30AAB64-BF28-4960-89C1-1F521025F531}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.UniverIcon", "src\components\BootstrapBlazor.UniverIcon\BootstrapBlazor.UniverIcon.csproj", "{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.ChatBot", "src\components\BootstrapBlazor.ChatBot\BootstrapBlazor.ChatBot.csproj", "{2F37FBF4-5C1C-4493-B614-0E8361432621}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Authenticator", "src\components\BootstrapBlazor.Authenticator\BootstrapBlazor.Authenticator.csproj", "{1FDDF0AD-7AB6-4706-A183-26C680817BB4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Graph", "src\components\BootstrapBlazor.Graph\BootstrapBlazor.Graph.csproj", "{CED55D86-57CF-CB0D-E880-370C44C0DB1F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Html2Pdf.Playwright", "src\components\BootstrapBlazor.Html2Pdf.Playwright\BootstrapBlazor.Html2Pdf.Playwright.csproj", "{F3043A78-1942-4524-BDC4-7E88F56DF3D5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.JitsiMeet", "src\components\BootstrapBlazor.JitsiMeet\BootstrapBlazor.JitsiMeet.csproj", "{08458CA3-BF81-48E8-870D-9389DC037808}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.PdfViewer", "src\components\BootstrapBlazor.PdfViewer\BootstrapBlazor.PdfViewer.csproj", "{4757B038-70E4-40B0-9B73-700EE5632B07}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Vditor", "src\components\BootstrapBlazor.Vditor\BootstrapBlazor.Vditor.csproj", "{D417E1B9-D146-4983-81D0-79F3193B322B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OfficeViewer", "src\components\BootstrapBlazor.OfficeViewer\BootstrapBlazor.OfficeViewer.csproj", "{2436940C-5920-D801-8A81-721F4C20A355}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Socket", "src\extensions\BootstrapBlazor.Socket\BootstrapBlazor.Socket.csproj", "{965F1512-57DC-4621-9C74-E059A14BB866}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.TcpSocket", "src\extensions\BootstrapBlazor.TcpSocket\BootstrapBlazor.TcpSocket.csproj", "{3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestTcpSocket", "test\UnitTestTcpSocket\UnitTestTcpSocket.csproj", "{10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestSvgIcon", "test\UnitTestSvgIcon\UnitTestSvgIcon.csproj", "{7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.FluentSystemIcon", "src\components\BootstrapBlazor.FluentSystemIcon\BootstrapBlazor.FluentSystemIcon.csproj", "{30449D30-0B4E-40FD-85BE-C9BAAC820162}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OpcUa", "src\components\BootstrapBlazor.OpcUa\BootstrapBlazor.OpcUa.csproj", "{6114A9DF-9DF5-474E-A5B0-25CDF0268B52}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestOpcUa", "test\UnitTestOpcUa\UnitTestOpcUa.csproj", "{98373A64-E224-4715-AE02-A8C6DAFF3338}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.OpcDa", "src\extensions\BootstrapBlazor.OpcDa\BootstrapBlazor.OpcDa.csproj", "{01007B10-7C3C-4136-83FF-981CA39AD3D4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestOpcDa", "test\UnitTestOpcDa\UnitTestOpcDa.csproj", "{835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.Tasks.Dashboard", "src\components\BootstrapBlazor.Tasks.Dashboard\BootstrapBlazor.Tasks.Dashboard.csproj", "{30C57119-C564-401C-AE3A-6203E2733E1A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0E6F09A3-61C4-4945-9DC8-5E00CE547DBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E6F09A3-61C4-4945-9DC8-5E00CE547DBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E6F09A3-61C4-4945-9DC8-5E00CE547DBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E6F09A3-61C4-4945-9DC8-5E00CE547DBD}.Release|Any CPU.Build.0 = Release|Any CPU - {203B9DE4-201C-4223-AD0E-4823777AB9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {203B9DE4-201C-4223-AD0E-4823777AB9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {203B9DE4-201C-4223-AD0E-4823777AB9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {203B9DE4-201C-4223-AD0E-4823777AB9A7}.Release|Any CPU.Build.0 = Release|Any CPU - {3EBD90E6-5237-4BB0-9C14-CC1FC5956A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3EBD90E6-5237-4BB0-9C14-CC1FC5956A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3EBD90E6-5237-4BB0-9C14-CC1FC5956A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3EBD90E6-5237-4BB0-9C14-CC1FC5956A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {079E4289-E2C6-47A9-98C2-9193F8A5D415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {079E4289-E2C6-47A9-98C2-9193F8A5D415}.Debug|Any CPU.Build.0 = Debug|Any CPU - {079E4289-E2C6-47A9-98C2-9193F8A5D415}.Release|Any CPU.ActiveCfg = Release|Any CPU - {079E4289-E2C6-47A9-98C2-9193F8A5D415}.Release|Any CPU.Build.0 = Release|Any CPU - {BF6F0A50-93DA-4F40-9936-933FEBF3B5CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF6F0A50-93DA-4F40-9936-933FEBF3B5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF6F0A50-93DA-4F40-9936-933FEBF3B5CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF6F0A50-93DA-4F40-9936-933FEBF3B5CA}.Release|Any CPU.Build.0 = Release|Any CPU - {A6FE7E0D-04EB-4E74-9C28-0B0920BEF7D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6FE7E0D-04EB-4E74-9C28-0B0920BEF7D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6FE7E0D-04EB-4E74-9C28-0B0920BEF7D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6FE7E0D-04EB-4E74-9C28-0B0920BEF7D4}.Release|Any CPU.Build.0 = Release|Any CPU - {72D0DB26-0437-499F-ACD6-4F90EA8513ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72D0DB26-0437-499F-ACD6-4F90EA8513ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72D0DB26-0437-499F-ACD6-4F90EA8513ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72D0DB26-0437-499F-ACD6-4F90EA8513ED}.Release|Any CPU.Build.0 = Release|Any CPU - {F979AFA9-E3CF-43DF-A7EA-3C63213D3C72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F979AFA9-E3CF-43DF-A7EA-3C63213D3C72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F979AFA9-E3CF-43DF-A7EA-3C63213D3C72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F979AFA9-E3CF-43DF-A7EA-3C63213D3C72}.Release|Any CPU.Build.0 = Release|Any CPU - {C1BD1FA6-BFD5-42E3-ADB5-4D4BA51653BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1BD1FA6-BFD5-42E3-ADB5-4D4BA51653BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1BD1FA6-BFD5-42E3-ADB5-4D4BA51653BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1BD1FA6-BFD5-42E3-ADB5-4D4BA51653BA}.Release|Any CPU.Build.0 = Release|Any CPU - {21A163D4-DC81-4B87-BA1B-9B5F48D66587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21A163D4-DC81-4B87-BA1B-9B5F48D66587}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21A163D4-DC81-4B87-BA1B-9B5F48D66587}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21A163D4-DC81-4B87-BA1B-9B5F48D66587}.Release|Any CPU.Build.0 = Release|Any CPU - {4E88785F-AD6A-4B57-BEC1-89F63A51BB63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E88785F-AD6A-4B57-BEC1-89F63A51BB63}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E88785F-AD6A-4B57-BEC1-89F63A51BB63}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E88785F-AD6A-4B57-BEC1-89F63A51BB63}.Release|Any CPU.Build.0 = Release|Any CPU - {D84EF7E9-2A5E-4EA8-BC2B-54289276A16D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D84EF7E9-2A5E-4EA8-BC2B-54289276A16D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D84EF7E9-2A5E-4EA8-BC2B-54289276A16D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D84EF7E9-2A5E-4EA8-BC2B-54289276A16D}.Release|Any CPU.Build.0 = Release|Any CPU - {CC30AFF5-305E-43C8-B230-1A82AE3041F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC30AFF5-305E-43C8-B230-1A82AE3041F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC30AFF5-305E-43C8-B230-1A82AE3041F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC30AFF5-305E-43C8-B230-1A82AE3041F2}.Release|Any CPU.Build.0 = Release|Any CPU - {CE85D4DD-6F4A-49AF-B575-2429FC545E7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE85D4DD-6F4A-49AF-B575-2429FC545E7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE85D4DD-6F4A-49AF-B575-2429FC545E7B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE85D4DD-6F4A-49AF-B575-2429FC545E7B}.Release|Any CPU.Build.0 = Release|Any CPU - {98BF777B-3AC1-4169-8633-F69D9A6EB275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98BF777B-3AC1-4169-8633-F69D9A6EB275}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98BF777B-3AC1-4169-8633-F69D9A6EB275}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98BF777B-3AC1-4169-8633-F69D9A6EB275}.Release|Any CPU.Build.0 = Release|Any CPU - {AB6EE78F-B778-42BA-91AB-234AC4F60C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB6EE78F-B778-42BA-91AB-234AC4F60C16}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB6EE78F-B778-42BA-91AB-234AC4F60C16}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB6EE78F-B778-42BA-91AB-234AC4F60C16}.Release|Any CPU.Build.0 = Release|Any CPU - {7FFCC03C-147B-4257-A035-2A31A807F173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7FFCC03C-147B-4257-A035-2A31A807F173}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7FFCC03C-147B-4257-A035-2A31A807F173}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7FFCC03C-147B-4257-A035-2A31A807F173}.Release|Any CPU.Build.0 = Release|Any CPU - {C546E031-1A92-409F-8E9C-4FD9A15FC042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C546E031-1A92-409F-8E9C-4FD9A15FC042}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C546E031-1A92-409F-8E9C-4FD9A15FC042}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C546E031-1A92-409F-8E9C-4FD9A15FC042}.Release|Any CPU.Build.0 = Release|Any CPU - {26DEC31C-40C2-4F3B-9A9D-2BEA313AA59C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26DEC31C-40C2-4F3B-9A9D-2BEA313AA59C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26DEC31C-40C2-4F3B-9A9D-2BEA313AA59C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26DEC31C-40C2-4F3B-9A9D-2BEA313AA59C}.Release|Any CPU.Build.0 = Release|Any CPU - {65181D1B-AD7C-4223-B230-6AB95EC1DA19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {65181D1B-AD7C-4223-B230-6AB95EC1DA19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {65181D1B-AD7C-4223-B230-6AB95EC1DA19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {65181D1B-AD7C-4223-B230-6AB95EC1DA19}.Release|Any CPU.Build.0 = Release|Any CPU - {FBC02566-6622-43DC-B105-A0C95E9D94EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBC02566-6622-43DC-B105-A0C95E9D94EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBC02566-6622-43DC-B105-A0C95E9D94EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBC02566-6622-43DC-B105-A0C95E9D94EB}.Release|Any CPU.Build.0 = Release|Any CPU - {14EBAC86-E192-4BFA-80C1-91F91BDDE6A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14EBAC86-E192-4BFA-80C1-91F91BDDE6A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14EBAC86-E192-4BFA-80C1-91F91BDDE6A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14EBAC86-E192-4BFA-80C1-91F91BDDE6A4}.Release|Any CPU.Build.0 = Release|Any CPU - {A5F1444E-D86F-4740-BADD-EF042193FD92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5F1444E-D86F-4740-BADD-EF042193FD92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5F1444E-D86F-4740-BADD-EF042193FD92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5F1444E-D86F-4740-BADD-EF042193FD92}.Release|Any CPU.Build.0 = Release|Any CPU - {C78E90E8-47EC-4A1E-A108-D0CD018CE417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C78E90E8-47EC-4A1E-A108-D0CD018CE417}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C78E90E8-47EC-4A1E-A108-D0CD018CE417}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C78E90E8-47EC-4A1E-A108-D0CD018CE417}.Release|Any CPU.Build.0 = Release|Any CPU - {A9D480F3-7D56-4E82-A71A-3378DE18A2D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9D480F3-7D56-4E82-A71A-3378DE18A2D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9D480F3-7D56-4E82-A71A-3378DE18A2D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9D480F3-7D56-4E82-A71A-3378DE18A2D2}.Release|Any CPU.Build.0 = Release|Any CPU - {A6B947E9-450C-45D7-BDC0-AE3C12D3FBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6B947E9-450C-45D7-BDC0-AE3C12D3FBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6B947E9-450C-45D7-BDC0-AE3C12D3FBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6B947E9-450C-45D7-BDC0-AE3C12D3FBA3}.Release|Any CPU.Build.0 = Release|Any CPU - {80A0135B-6F5B-4CB0-90F5-0060D241136A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80A0135B-6F5B-4CB0-90F5-0060D241136A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80A0135B-6F5B-4CB0-90F5-0060D241136A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80A0135B-6F5B-4CB0-90F5-0060D241136A}.Release|Any CPU.Build.0 = Release|Any CPU - {B0D2BBC7-D1BB-4F7C-ADE0-59BEBF3BB5D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0D2BBC7-D1BB-4F7C-ADE0-59BEBF3BB5D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0D2BBC7-D1BB-4F7C-ADE0-59BEBF3BB5D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0D2BBC7-D1BB-4F7C-ADE0-59BEBF3BB5D0}.Release|Any CPU.Build.0 = Release|Any CPU - {4B71D8F7-CF9C-44CE-B371-45FBFEE0FB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B71D8F7-CF9C-44CE-B371-45FBFEE0FB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B71D8F7-CF9C-44CE-B371-45FBFEE0FB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B71D8F7-CF9C-44CE-B371-45FBFEE0FB6E}.Release|Any CPU.Build.0 = Release|Any CPU - {F538D58D-58B1-4BA2-A933-E91AC168F568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F538D58D-58B1-4BA2-A933-E91AC168F568}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F538D58D-58B1-4BA2-A933-E91AC168F568}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F538D58D-58B1-4BA2-A933-E91AC168F568}.Release|Any CPU.Build.0 = Release|Any CPU - {BB03EAB9-9B5F-49E2-87CC-E61A06AF2DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB03EAB9-9B5F-49E2-87CC-E61A06AF2DE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB03EAB9-9B5F-49E2-87CC-E61A06AF2DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB03EAB9-9B5F-49E2-87CC-E61A06AF2DE7}.Release|Any CPU.Build.0 = Release|Any CPU - {309D35E1-8B17-4488-93BC-D6B301B1F853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {309D35E1-8B17-4488-93BC-D6B301B1F853}.Debug|Any CPU.Build.0 = Debug|Any CPU - {309D35E1-8B17-4488-93BC-D6B301B1F853}.Release|Any CPU.ActiveCfg = Release|Any CPU - {309D35E1-8B17-4488-93BC-D6B301B1F853}.Release|Any CPU.Build.0 = Release|Any CPU - {CC7C4278-BC2E-49D0-BE18-BF8D28EFE6E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC7C4278-BC2E-49D0-BE18-BF8D28EFE6E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC7C4278-BC2E-49D0-BE18-BF8D28EFE6E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC7C4278-BC2E-49D0-BE18-BF8D28EFE6E7}.Release|Any CPU.Build.0 = Release|Any CPU - {BB7FBAC6-0F98-4443-B3A0-B7D0F4164D20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB7FBAC6-0F98-4443-B3A0-B7D0F4164D20}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB7FBAC6-0F98-4443-B3A0-B7D0F4164D20}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB7FBAC6-0F98-4443-B3A0-B7D0F4164D20}.Release|Any CPU.Build.0 = Release|Any CPU - {162A2C8E-767C-41A7-9020-729E79C4F1F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {162A2C8E-767C-41A7-9020-729E79C4F1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {162A2C8E-767C-41A7-9020-729E79C4F1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {162A2C8E-767C-41A7-9020-729E79C4F1F9}.Release|Any CPU.Build.0 = Release|Any CPU - {E386EBD8-0028-42B7-8473-F26014C0E326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E386EBD8-0028-42B7-8473-F26014C0E326}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E386EBD8-0028-42B7-8473-F26014C0E326}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E386EBD8-0028-42B7-8473-F26014C0E326}.Release|Any CPU.Build.0 = Release|Any CPU - {8B374E31-BBC3-4765-82C7-E124C4E1E731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B374E31-BBC3-4765-82C7-E124C4E1E731}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B374E31-BBC3-4765-82C7-E124C4E1E731}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B374E31-BBC3-4765-82C7-E124C4E1E731}.Release|Any CPU.Build.0 = Release|Any CPU - {496554A7-52B0-4E01-9CF3-08F0A2B2FCD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {496554A7-52B0-4E01-9CF3-08F0A2B2FCD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {496554A7-52B0-4E01-9CF3-08F0A2B2FCD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {496554A7-52B0-4E01-9CF3-08F0A2B2FCD8}.Release|Any CPU.Build.0 = Release|Any CPU - {7C875492-41A2-401D-90B1-161BCD5103DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C875492-41A2-401D-90B1-161BCD5103DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C875492-41A2-401D-90B1-161BCD5103DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C875492-41A2-401D-90B1-161BCD5103DC}.Release|Any CPU.Build.0 = Release|Any CPU - {126D5C86-2C33-4E79-A762-DBEA6E7A779E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {126D5C86-2C33-4E79-A762-DBEA6E7A779E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {126D5C86-2C33-4E79-A762-DBEA6E7A779E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {126D5C86-2C33-4E79-A762-DBEA6E7A779E}.Release|Any CPU.Build.0 = Release|Any CPU - {7DD55FF1-54BF-4B4C-9B74-603C79D9EC07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DD55FF1-54BF-4B4C-9B74-603C79D9EC07}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DD55FF1-54BF-4B4C-9B74-603C79D9EC07}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DD55FF1-54BF-4B4C-9B74-603C79D9EC07}.Release|Any CPU.Build.0 = Release|Any CPU - {2D0F834A-522F-4CF3-A6BB-BC2585D8505E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D0F834A-522F-4CF3-A6BB-BC2585D8505E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D0F834A-522F-4CF3-A6BB-BC2585D8505E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D0F834A-522F-4CF3-A6BB-BC2585D8505E}.Release|Any CPU.Build.0 = Release|Any CPU - {9F61C73D-EFF4-4226-ADEF-4B32F03AA371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F61C73D-EFF4-4226-ADEF-4B32F03AA371}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F61C73D-EFF4-4226-ADEF-4B32F03AA371}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F61C73D-EFF4-4226-ADEF-4B32F03AA371}.Release|Any CPU.Build.0 = Release|Any CPU - {4D0A145A-029E-4DD9-841F-302E8B2AB143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D0A145A-029E-4DD9-841F-302E8B2AB143}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D0A145A-029E-4DD9-841F-302E8B2AB143}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D0A145A-029E-4DD9-841F-302E8B2AB143}.Release|Any CPU.Build.0 = Release|Any CPU - {150033CC-28E8-47B2-9704-F6203964B5F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {150033CC-28E8-47B2-9704-F6203964B5F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {150033CC-28E8-47B2-9704-F6203964B5F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {150033CC-28E8-47B2-9704-F6203964B5F1}.Release|Any CPU.Build.0 = Release|Any CPU - {AC89886C-4216-4D20-B8DD-CFEAFC6AB2C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC89886C-4216-4D20-B8DD-CFEAFC6AB2C8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC89886C-4216-4D20-B8DD-CFEAFC6AB2C8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC89886C-4216-4D20-B8DD-CFEAFC6AB2C8}.Release|Any CPU.Build.0 = Release|Any CPU - {45F1D4EB-AFB6-484B-90D3-DB684B76E693}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45F1D4EB-AFB6-484B-90D3-DB684B76E693}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45F1D4EB-AFB6-484B-90D3-DB684B76E693}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45F1D4EB-AFB6-484B-90D3-DB684B76E693}.Release|Any CPU.Build.0 = Release|Any CPU - {2D702BA7-AB23-46BD-9F54-7F7E28322790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D702BA7-AB23-46BD-9F54-7F7E28322790}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D702BA7-AB23-46BD-9F54-7F7E28322790}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D702BA7-AB23-46BD-9F54-7F7E28322790}.Release|Any CPU.Build.0 = Release|Any CPU - {A8113292-7E73-417A-8E23-9F69F5577816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8113292-7E73-417A-8E23-9F69F5577816}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8113292-7E73-417A-8E23-9F69F5577816}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8113292-7E73-417A-8E23-9F69F5577816}.Release|Any CPU.Build.0 = Release|Any CPU - {2662E3C2-F511-4DE0-AA72-FE453031993F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2662E3C2-F511-4DE0-AA72-FE453031993F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2662E3C2-F511-4DE0-AA72-FE453031993F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2662E3C2-F511-4DE0-AA72-FE453031993F}.Release|Any CPU.Build.0 = Release|Any CPU - {9BFFB509-9B57-4FE7-B2A1-4D6AFEEE0067}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BFFB509-9B57-4FE7-B2A1-4D6AFEEE0067}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BFFB509-9B57-4FE7-B2A1-4D6AFEEE0067}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BFFB509-9B57-4FE7-B2A1-4D6AFEEE0067}.Release|Any CPU.Build.0 = Release|Any CPU - {2E338270-C302-4E13-A681-7F80E1BEE158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E338270-C302-4E13-A681-7F80E1BEE158}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E338270-C302-4E13-A681-7F80E1BEE158}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E338270-C302-4E13-A681-7F80E1BEE158}.Release|Any CPU.Build.0 = Release|Any CPU - {750E2849-EA95-4849-A7E7-09920E30765D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {750E2849-EA95-4849-A7E7-09920E30765D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {750E2849-EA95-4849-A7E7-09920E30765D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {750E2849-EA95-4849-A7E7-09920E30765D}.Release|Any CPU.Build.0 = Release|Any CPU - {37979C83-5E5F-4525-85D7-DA20B27F831F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {37979C83-5E5F-4525-85D7-DA20B27F831F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {37979C83-5E5F-4525-85D7-DA20B27F831F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {37979C83-5E5F-4525-85D7-DA20B27F831F}.Release|Any CPU.Build.0 = Release|Any CPU - {22594AD2-3E36-4984-949C-8B578B402EDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22594AD2-3E36-4984-949C-8B578B402EDE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22594AD2-3E36-4984-949C-8B578B402EDE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22594AD2-3E36-4984-949C-8B578B402EDE}.Release|Any CPU.Build.0 = Release|Any CPU - {2D783293-CEBD-4365-B8A1-0F9E76D73295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D783293-CEBD-4365-B8A1-0F9E76D73295}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D783293-CEBD-4365-B8A1-0F9E76D73295}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D783293-CEBD-4365-B8A1-0F9E76D73295}.Release|Any CPU.Build.0 = Release|Any CPU - {11FD9DF2-3F3F-4AF1-8C85-446190F7D887}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {11FD9DF2-3F3F-4AF1-8C85-446190F7D887}.Debug|Any CPU.Build.0 = Debug|Any CPU - {11FD9DF2-3F3F-4AF1-8C85-446190F7D887}.Release|Any CPU.ActiveCfg = Release|Any CPU - {11FD9DF2-3F3F-4AF1-8C85-446190F7D887}.Release|Any CPU.Build.0 = Release|Any CPU - {29E47F4A-CE03-42B5-BDAA-FB4B40D4C897}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29E47F4A-CE03-42B5-BDAA-FB4B40D4C897}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29E47F4A-CE03-42B5-BDAA-FB4B40D4C897}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29E47F4A-CE03-42B5-BDAA-FB4B40D4C897}.Release|Any CPU.Build.0 = Release|Any CPU - {DA2198E1-2CA9-EE53-926B-7950AB4B5EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA2198E1-2CA9-EE53-926B-7950AB4B5EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA2198E1-2CA9-EE53-926B-7950AB4B5EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA2198E1-2CA9-EE53-926B-7950AB4B5EBF}.Release|Any CPU.Build.0 = Release|Any CPU - {4B086A62-5F5A-47BC-921F-35803F26DD68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B086A62-5F5A-47BC-921F-35803F26DD68}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B086A62-5F5A-47BC-921F-35803F26DD68}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B086A62-5F5A-47BC-921F-35803F26DD68}.Release|Any CPU.Build.0 = Release|Any CPU - {7328E464-AE3C-4277-BEC3-422C56637066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7328E464-AE3C-4277-BEC3-422C56637066}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7328E464-AE3C-4277-BEC3-422C56637066}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7328E464-AE3C-4277-BEC3-422C56637066}.Release|Any CPU.Build.0 = Release|Any CPU - {84823875-1B07-4CCE-A009-29AEF90C6C10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84823875-1B07-4CCE-A009-29AEF90C6C10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84823875-1B07-4CCE-A009-29AEF90C6C10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84823875-1B07-4CCE-A009-29AEF90C6C10}.Release|Any CPU.Build.0 = Release|Any CPU - {AA4EDA37-1D81-4235-A7F6-F1C112B364EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA4EDA37-1D81-4235-A7F6-F1C112B364EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA4EDA37-1D81-4235-A7F6-F1C112B364EF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA4EDA37-1D81-4235-A7F6-F1C112B364EF}.Release|Any CPU.Build.0 = Release|Any CPU - {8A46FEDF-FD76-48AB-9BB2-47254C7623A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A46FEDF-FD76-48AB-9BB2-47254C7623A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A46FEDF-FD76-48AB-9BB2-47254C7623A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A46FEDF-FD76-48AB-9BB2-47254C7623A4}.Release|Any CPU.Build.0 = Release|Any CPU - {C882891F-4A6A-C9BE-AC96-227764A4A46A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C882891F-4A6A-C9BE-AC96-227764A4A46A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C882891F-4A6A-C9BE-AC96-227764A4A46A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C882891F-4A6A-C9BE-AC96-227764A4A46A}.Release|Any CPU.Build.0 = Release|Any CPU - {F184D96E-7855-4E3B-B447-D09DBC1C91C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F184D96E-7855-4E3B-B447-D09DBC1C91C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F184D96E-7855-4E3B-B447-D09DBC1C91C6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F184D96E-7855-4E3B-B447-D09DBC1C91C6}.Release|Any CPU.Build.0 = Release|Any CPU - {E30AAB64-BF28-4960-89C1-1F521025F531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E30AAB64-BF28-4960-89C1-1F521025F531}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E30AAB64-BF28-4960-89C1-1F521025F531}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E30AAB64-BF28-4960-89C1-1F521025F531}.Release|Any CPU.Build.0 = Release|Any CPU - {A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Release|Any CPU.Build.0 = Release|Any CPU - {2F37FBF4-5C1C-4493-B614-0E8361432621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F37FBF4-5C1C-4493-B614-0E8361432621}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F37FBF4-5C1C-4493-B614-0E8361432621}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F37FBF4-5C1C-4493-B614-0E8361432621}.Release|Any CPU.Build.0 = Release|Any CPU - {1FDDF0AD-7AB6-4706-A183-26C680817BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FDDF0AD-7AB6-4706-A183-26C680817BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FDDF0AD-7AB6-4706-A183-26C680817BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FDDF0AD-7AB6-4706-A183-26C680817BB4}.Release|Any CPU.Build.0 = Release|Any CPU - {CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CED55D86-57CF-CB0D-E880-370C44C0DB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3043A78-1942-4524-BDC4-7E88F56DF3D5}.Release|Any CPU.Build.0 = Release|Any CPU - {08458CA3-BF81-48E8-870D-9389DC037808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08458CA3-BF81-48E8-870D-9389DC037808}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08458CA3-BF81-48E8-870D-9389DC037808}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08458CA3-BF81-48E8-870D-9389DC037808}.Release|Any CPU.Build.0 = Release|Any CPU - {4757B038-70E4-40B0-9B73-700EE5632B07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4757B038-70E4-40B0-9B73-700EE5632B07}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4757B038-70E4-40B0-9B73-700EE5632B07}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4757B038-70E4-40B0-9B73-700EE5632B07}.Release|Any CPU.Build.0 = Release|Any CPU - {D417E1B9-D146-4983-81D0-79F3193B322B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D417E1B9-D146-4983-81D0-79F3193B322B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D417E1B9-D146-4983-81D0-79F3193B322B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D417E1B9-D146-4983-81D0-79F3193B322B}.Release|Any CPU.Build.0 = Release|Any CPU - {2436940C-5920-D801-8A81-721F4C20A355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2436940C-5920-D801-8A81-721F4C20A355}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2436940C-5920-D801-8A81-721F4C20A355}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2436940C-5920-D801-8A81-721F4C20A355}.Release|Any CPU.Build.0 = Release|Any CPU - {965F1512-57DC-4621-9C74-E059A14BB866}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {965F1512-57DC-4621-9C74-E059A14BB866}.Debug|Any CPU.Build.0 = Debug|Any CPU - {965F1512-57DC-4621-9C74-E059A14BB866}.Release|Any CPU.ActiveCfg = Release|Any CPU - {965F1512-57DC-4621-9C74-E059A14BB866}.Release|Any CPU.Build.0 = Release|Any CPU - {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C}.Release|Any CPU.Build.0 = Release|Any CPU - {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E}.Release|Any CPU.Build.0 = Release|Any CPU - {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3}.Release|Any CPU.Build.0 = Release|Any CPU - {30449D30-0B4E-40FD-85BE-C9BAAC820162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30449D30-0B4E-40FD-85BE-C9BAAC820162}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30449D30-0B4E-40FD-85BE-C9BAAC820162}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30449D30-0B4E-40FD-85BE-C9BAAC820162}.Release|Any CPU.Build.0 = Release|Any CPU - {6114A9DF-9DF5-474E-A5B0-25CDF0268B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6114A9DF-9DF5-474E-A5B0-25CDF0268B52}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6114A9DF-9DF5-474E-A5B0-25CDF0268B52}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6114A9DF-9DF5-474E-A5B0-25CDF0268B52}.Release|Any CPU.Build.0 = Release|Any CPU - {98373A64-E224-4715-AE02-A8C6DAFF3338}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98373A64-E224-4715-AE02-A8C6DAFF3338}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98373A64-E224-4715-AE02-A8C6DAFF3338}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98373A64-E224-4715-AE02-A8C6DAFF3338}.Release|Any CPU.Build.0 = Release|Any CPU - {01007B10-7C3C-4136-83FF-981CA39AD3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01007B10-7C3C-4136-83FF-981CA39AD3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01007B10-7C3C-4136-83FF-981CA39AD3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01007B10-7C3C-4136-83FF-981CA39AD3D4}.Release|Any CPU.Build.0 = Release|Any CPU - {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86}.Release|Any CPU.Build.0 = Release|Any CPU - {30C57119-C564-401C-AE3A-6203E2733E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30C57119-C564-401C-AE3A-6203E2733E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30C57119-C564-401C-AE3A-6203E2733E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30C57119-C564-401C-AE3A-6203E2733E1A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {FF1089BE-C704-4374-B629-C57C08E1798F} = {A48E5EAB-6EFB-4A78-810E-A3E79CF749F5} - {0E6F09A3-61C4-4945-9DC8-5E00CE547DBD} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {203B9DE4-201C-4223-AD0E-4823777AB9A7} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {3EBD90E6-5237-4BB0-9C14-CC1FC5956A1B} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {079E4289-E2C6-47A9-98C2-9193F8A5D415} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {BF6F0A50-93DA-4F40-9936-933FEBF3B5CA} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {A6FE7E0D-04EB-4E74-9C28-0B0920BEF7D4} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {72D0DB26-0437-499F-ACD6-4F90EA8513ED} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {F979AFA9-E3CF-43DF-A7EA-3C63213D3C72} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {C1BD1FA6-BFD5-42E3-ADB5-4D4BA51653BA} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {21A163D4-DC81-4B87-BA1B-9B5F48D66587} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {4E88785F-AD6A-4B57-BEC1-89F63A51BB63} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {D84EF7E9-2A5E-4EA8-BC2B-54289276A16D} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {CC30AFF5-305E-43C8-B230-1A82AE3041F2} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {CE85D4DD-6F4A-49AF-B575-2429FC545E7B} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {98BF777B-3AC1-4169-8633-F69D9A6EB275} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {AB6EE78F-B778-42BA-91AB-234AC4F60C16} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {7FFCC03C-147B-4257-A035-2A31A807F173} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {C546E031-1A92-409F-8E9C-4FD9A15FC042} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {26DEC31C-40C2-4F3B-9A9D-2BEA313AA59C} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {65181D1B-AD7C-4223-B230-6AB95EC1DA19} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {FBC02566-6622-43DC-B105-A0C95E9D94EB} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {14EBAC86-E192-4BFA-80C1-91F91BDDE6A4} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {A5F1444E-D86F-4740-BADD-EF042193FD92} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {C78E90E8-47EC-4A1E-A108-D0CD018CE417} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {A9D480F3-7D56-4E82-A71A-3378DE18A2D2} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {A6B947E9-450C-45D7-BDC0-AE3C12D3FBA3} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {80A0135B-6F5B-4CB0-90F5-0060D241136A} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {B0D2BBC7-D1BB-4F7C-ADE0-59BEBF3BB5D0} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {4B71D8F7-CF9C-44CE-B371-45FBFEE0FB6E} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {F538D58D-58B1-4BA2-A933-E91AC168F568} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {BB03EAB9-9B5F-49E2-87CC-E61A06AF2DE7} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {309D35E1-8B17-4488-93BC-D6B301B1F853} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {CC7C4278-BC2E-49D0-BE18-BF8D28EFE6E7} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {BB7FBAC6-0F98-4443-B3A0-B7D0F4164D20} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {162A2C8E-767C-41A7-9020-729E79C4F1F9} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {E386EBD8-0028-42B7-8473-F26014C0E326} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {8B374E31-BBC3-4765-82C7-E124C4E1E731} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {496554A7-52B0-4E01-9CF3-08F0A2B2FCD8} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {7C875492-41A2-401D-90B1-161BCD5103DC} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {126D5C86-2C33-4E79-A762-DBEA6E7A779E} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {7DD55FF1-54BF-4B4C-9B74-603C79D9EC07} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {2D0F834A-522F-4CF3-A6BB-BC2585D8505E} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {9F61C73D-EFF4-4226-ADEF-4B32F03AA371} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {4D0A145A-029E-4DD9-841F-302E8B2AB143} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {150033CC-28E8-47B2-9704-F6203964B5F1} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {AC89886C-4216-4D20-B8DD-CFEAFC6AB2C8} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {45F1D4EB-AFB6-484B-90D3-DB684B76E693} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {2D702BA7-AB23-46BD-9F54-7F7E28322790} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {A8113292-7E73-417A-8E23-9F69F5577816} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} = {A48E5EAB-6EFB-4A78-810E-A3E79CF749F5} - {2662E3C2-F511-4DE0-AA72-FE453031993F} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {9BFFB509-9B57-4FE7-B2A1-4D6AFEEE0067} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {2E338270-C302-4E13-A681-7F80E1BEE158} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {750E2849-EA95-4849-A7E7-09920E30765D} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {BB06AD5B-3F2B-4B59-A135-36AF999A614F} = {A48E5EAB-6EFB-4A78-810E-A3E79CF749F5} - {37979C83-5E5F-4525-85D7-DA20B27F831F} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} - {22594AD2-3E36-4984-949C-8B578B402EDE} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} - {FF8B3A19-60DC-4802-9FA0-9FE670C7D565} = {A48E5EAB-6EFB-4A78-810E-A3E79CF749F5} - {2D783293-CEBD-4365-B8A1-0F9E76D73295} = {BB06AD5B-3F2B-4B59-A135-36AF999A614F} - {11FD9DF2-3F3F-4AF1-8C85-446190F7D887} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {29E47F4A-CE03-42B5-BDAA-FB4B40D4C897} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {DA2198E1-2CA9-EE53-926B-7950AB4B5EBF} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {4B086A62-5F5A-47BC-921F-35803F26DD68} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {7328E464-AE3C-4277-BEC3-422C56637066} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {84823875-1B07-4CCE-A009-29AEF90C6C10} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {AA4EDA37-1D81-4235-A7F6-F1C112B364EF} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {8A46FEDF-FD76-48AB-9BB2-47254C7623A4} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {C882891F-4A6A-C9BE-AC96-227764A4A46A} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {F184D96E-7855-4E3B-B447-D09DBC1C91C6} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {E30AAB64-BF28-4960-89C1-1F521025F531} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {A657E04C-1495-439E-BC2E-1EEAB2D1B4DA} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {2F37FBF4-5C1C-4493-B614-0E8361432621} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {1FDDF0AD-7AB6-4706-A183-26C680817BB4} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {CED55D86-57CF-CB0D-E880-370C44C0DB1F} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {F3043A78-1942-4524-BDC4-7E88F56DF3D5} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {08458CA3-BF81-48E8-870D-9389DC037808} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {4757B038-70E4-40B0-9B73-700EE5632B07} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {D417E1B9-D146-4983-81D0-79F3193B322B} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {2436940C-5920-D801-8A81-721F4C20A355} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {965F1512-57DC-4621-9C74-E059A14BB866} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {3F7C6E3F-5AC2-4B13-A57F-9329E34C1F5C} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {10D35EE5-FA31-4C80-B113-CD7A0FB76B4E} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} - {7CAD5915-CE3E-31ED-B1AC-15C61C3ED8C3} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} - {30449D30-0B4E-40FD-85BE-C9BAAC820162} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {6114A9DF-9DF5-474E-A5B0-25CDF0268B52} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {98373A64-E224-4715-AE02-A8C6DAFF3338} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} - {01007B10-7C3C-4136-83FF-981CA39AD3D4} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - {835C8BA9-A9CC-4EA0-9002-34A20F8B2E86} = {B6A98ADE-D26A-4D0B-8978-AB7AC915F5AE} - {30C57119-C564-401C-AE3A-6203E2733E1A} = {FF1089BE-C704-4374-B629-C57C08E1798F} - {98054DCC-A9AB-CB11-798F-424112EC8639} = {7B29E81D-92DE-46C8-8EDC-1B48C8F12BC2} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF} - EndGlobalSection -EndGlobal