mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 18:51:28 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5aff6461a1 | ||
![]() |
6cf53fefec | ||
![]() |
45132f3503 | ||
![]() |
f1e78a0e8a | ||
![]() |
0bf28ec275 | ||
![]() |
41f8412c97 | ||
![]() |
c535974362 | ||
![]() |
1860c5f215 | ||
![]() |
6d778b2d39 | ||
![]() |
f48b99c259 | ||
![]() |
3c73b93051 |
@@ -169,7 +169,7 @@ internal sealed class VerificatInfoService : BaseService<VerificatInfo>, IVerifi
|
||||
public void RemoveAllClientId()
|
||||
{
|
||||
using var db = GetDB();
|
||||
db.Updateable<VerificatInfo>().SetColumns("ClientIds", new ConcurrentList<long>().ToJsonNetString()).Where(a => a.Id >= 0).ExecuteCommand();
|
||||
db.Updateable<VerificatInfo>().SetColumns(a=>a.ClientIds==null).Where(a => a.Id > 0).ExecuteCommand();
|
||||
VerificatInfoService.RemoveCache();
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,11 @@ public sealed class SqlSugarOption : ConnectionConfig
|
||||
/// </summary>
|
||||
public bool InitSeedData { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化数据库
|
||||
/// </summary>
|
||||
public bool InitDatabase { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表
|
||||
/// </summary>
|
||||
|
@@ -89,7 +89,7 @@ public class Startup : AppStartup
|
||||
DbContext.DbConfigs?.ForEach(it =>
|
||||
{
|
||||
var connection = DbContext.Db.GetConnection(it.ConfigId);//获取数据库连接对象
|
||||
if (it.InitTable == true)
|
||||
if (it.InitDatabase == true)
|
||||
connection.DbMaintenance.CreateDatabase();//创建数据库,如果存在则不创建
|
||||
});
|
||||
|
||||
|
@@ -0,0 +1,81 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using ThingsGateway;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// HostApplication 拓展
|
||||
/// </summary>
|
||||
public static class AppHostApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Host 应用注入
|
||||
/// </summary>
|
||||
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
|
||||
/// <param name="autoRegisterBackgroundService"></param>
|
||||
/// <returns>HostApplicationBuilder</returns>
|
||||
public static HostApplicationBuilder Inject(this HostApplicationBuilder hostApplicationBuilder, bool autoRegisterBackgroundService = true)
|
||||
{
|
||||
// 初始化配置
|
||||
InternalApp.ConfigureApplication(hostApplicationBuilder, autoRegisterBackgroundService);
|
||||
|
||||
return hostApplicationBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册依赖组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">派生自 <see cref="IServiceComponent"/></typeparam>
|
||||
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns></returns>
|
||||
public static HostApplicationBuilder AddComponent<TComponent>(this HostApplicationBuilder hostApplicationBuilder, object options = default)
|
||||
where TComponent : class, IServiceComponent, new()
|
||||
{
|
||||
hostApplicationBuilder.Services.AddComponent<TComponent>(options);
|
||||
|
||||
return hostApplicationBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册依赖组件
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">派生自 <see cref="IServiceComponent"/></typeparam>
|
||||
/// <typeparam name="TComponentOptions">组件参数</typeparam>
|
||||
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns><see cref="HostApplicationBuilder"/></returns>
|
||||
public static HostApplicationBuilder AddComponent<TComponent, TComponentOptions>(this HostApplicationBuilder hostApplicationBuilder, TComponentOptions options = default)
|
||||
where TComponent : class, IServiceComponent, new()
|
||||
{
|
||||
hostApplicationBuilder.Services.AddComponent<TComponent, TComponentOptions>(options);
|
||||
|
||||
return hostApplicationBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册依赖组件
|
||||
/// </summary>
|
||||
/// <param name="hostApplicationBuilder">Host 应用构建器</param>
|
||||
/// <param name="componentType">组件类型</param>
|
||||
/// <param name="options">组件参数</param>
|
||||
/// <returns><see cref="HostApplicationBuilder"/></returns>
|
||||
public static HostApplicationBuilder AddComponent(this HostApplicationBuilder hostApplicationBuilder, Type componentType, object options = default)
|
||||
{
|
||||
hostApplicationBuilder.Services.AddComponent(componentType, options);
|
||||
|
||||
return hostApplicationBuilder;
|
||||
}
|
||||
}
|
@@ -467,18 +467,20 @@ public static class ObjectExtensions
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 查找方法指定特性,如果没找到则继续查找声明类
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute"></typeparam>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="inherit"></param>
|
||||
/// <param name="searchFromReflectedType">searchFromRuntimeType</param>
|
||||
/// <returns></returns>
|
||||
internal static TAttribute GetFoundAttribute<TAttribute>(this MethodInfo method, bool inherit)
|
||||
internal static TAttribute GetFoundAttribute<TAttribute>(this MethodInfo method, bool inherit, bool searchFromReflectedType = false)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
// 获取方法所在类型
|
||||
var declaringType = method.DeclaringType;
|
||||
var declaringType = !searchFromReflectedType ? method.DeclaringType : method.ReflectedType; // 解决嵌套继承问题
|
||||
|
||||
var attributeType = typeof(TAttribute);
|
||||
|
||||
@@ -493,7 +495,6 @@ public static class ObjectExtensions
|
||||
|
||||
return foundAttribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化字符串
|
||||
/// </summary>
|
||||
|
@@ -132,6 +132,34 @@ internal static class InternalApp
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 Furion 框架(非 Web)
|
||||
/// </summary>
|
||||
/// <param name="hostApplicationBuilder"></param>
|
||||
/// <param name="autoRegisterBackgroundService"></param>
|
||||
internal static void ConfigureApplication(IHostApplicationBuilder hostApplicationBuilder, bool autoRegisterBackgroundService = true)
|
||||
{
|
||||
// 存储环境对象
|
||||
HostEnvironment = hostApplicationBuilder.Environment;
|
||||
|
||||
// 加载配置
|
||||
AddJsonFiles(hostApplicationBuilder.Configuration, hostApplicationBuilder.Environment);
|
||||
|
||||
// 存储配置对象
|
||||
Configuration = hostApplicationBuilder.Configuration;
|
||||
|
||||
// 存储服务提供器
|
||||
InternalServices = hostApplicationBuilder.Services;
|
||||
|
||||
// 存储根服务
|
||||
hostApplicationBuilder.Services.AddHostedService<GenericHostLifetimeEventsHostedService>();
|
||||
|
||||
// 初始化应用服务
|
||||
hostApplicationBuilder.Services.AddApp();
|
||||
|
||||
// 自动注册 BackgroundService
|
||||
if (autoRegisterBackgroundService) hostApplicationBuilder.Services.AddAppHostedService();
|
||||
}
|
||||
/// <summary>
|
||||
/// 自动装载主机配置
|
||||
/// </summary>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@@ -29,34 +29,36 @@ public class AESEncryption
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns></returns>
|
||||
public static string Encrypt(string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static string Encrypt(string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
var bKey = Encoding.UTF8.GetBytes(skey);
|
||||
var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
|
||||
if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
|
||||
|
||||
using var aesAlg = Aes.Create();
|
||||
aesAlg.Key = bKey;
|
||||
aesAlg.Mode = mode;
|
||||
aesAlg.Padding = padding;
|
||||
|
||||
// 如果是 ECB 模式,不需要 IV
|
||||
if (mode != CipherMode.ECB)
|
||||
{
|
||||
aesAlg.IV = iv ?? aesAlg.IV; // 如果未提供 IV,则使用随机生成的 IV
|
||||
aesAlg.IV = iv ?? aesAlg.IV;
|
||||
if (iv != null && iv.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
|
||||
}
|
||||
|
||||
using var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
|
||||
using var encryptor = aesAlg.CreateEncryptor();
|
||||
using var msEncrypt = new MemoryStream();
|
||||
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
|
||||
using (var swEncrypt = new StreamWriter(csEncrypt))
|
||||
using (var swEncrypt = new StreamWriter(csEncrypt, Encoding.UTF8))
|
||||
{
|
||||
swEncrypt.Write(text);
|
||||
}
|
||||
|
||||
var encryptedContent = msEncrypt.ToArray();
|
||||
|
||||
// 如果是 CBC 模式,将 IV 和密文拼接在一起
|
||||
if (mode != CipherMode.ECB)
|
||||
// 仅在未提供 IV 时拼接 IV
|
||||
if (mode != CipherMode.ECB && iv == null)
|
||||
{
|
||||
var result = new byte[aesAlg.IV.Length + encryptedContent.Length];
|
||||
Buffer.BlockCopy(aesAlg.IV, 0, result, 0, aesAlg.IV.Length);
|
||||
@@ -76,35 +78,43 @@ public class AESEncryption
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns></returns>
|
||||
public static string Decrypt(string hash, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static string Decrypt(string hash, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
var fullCipher = Convert.FromBase64String(hash);
|
||||
|
||||
var bKey = Encoding.UTF8.GetBytes(skey);
|
||||
var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
|
||||
if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
|
||||
|
||||
using var aesAlg = Aes.Create();
|
||||
aesAlg.Key = bKey;
|
||||
aesAlg.Mode = mode;
|
||||
aesAlg.Padding = padding;
|
||||
|
||||
// 如果是 ECB 模式,不需要 IV
|
||||
if (mode != CipherMode.ECB)
|
||||
{
|
||||
var bVector = new byte[16];
|
||||
var cipher = new byte[fullCipher.Length - bVector.Length];
|
||||
if (iv == null)
|
||||
{
|
||||
if (fullCipher.Length < aesAlg.BlockSize / 8) throw new ArgumentException("The ciphertext length is insufficient to extract the IV.");
|
||||
|
||||
Unsafe.CopyBlock(ref bVector[0], ref fullCipher[0], (uint)bVector.Length);
|
||||
Unsafe.CopyBlock(ref cipher[0], ref fullCipher[bVector.Length], (uint)(fullCipher.Length - bVector.Length));
|
||||
|
||||
aesAlg.IV = iv ?? bVector;
|
||||
fullCipher = cipher;
|
||||
iv = new byte[aesAlg.BlockSize / 8];
|
||||
var cipher = new byte[fullCipher.Length - iv.Length];
|
||||
Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length);
|
||||
Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, cipher.Length);
|
||||
aesAlg.IV = iv;
|
||||
fullCipher = cipher;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iv.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
|
||||
aesAlg.IV = iv;
|
||||
}
|
||||
}
|
||||
|
||||
using var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
|
||||
using var decryptor = aesAlg.CreateDecryptor();
|
||||
using var msDecrypt = new MemoryStream(fullCipher);
|
||||
using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
|
||||
using var srDecrypt = new StreamReader(csDecrypt);
|
||||
using var srDecrypt = new StreamReader(csDecrypt, Encoding.UTF8);
|
||||
|
||||
return srDecrypt.ReadToEnd();
|
||||
}
|
||||
@@ -117,19 +127,13 @@ public class AESEncryption
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns>加密后的字节数组</returns>
|
||||
public static byte[] Encrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static byte[] Encrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
// 确保密钥长度为 128 位、192 位或 256 位
|
||||
var bKey = new byte[32]; // 256 位密钥
|
||||
var keyBytes = Encoding.UTF8.GetBytes(skey);
|
||||
Array.Copy(keyBytes, bKey, Math.Min(keyBytes.Length, bKey.Length));
|
||||
|
||||
// 如果是 ECB 模式,不需要 IV
|
||||
if (mode != CipherMode.ECB)
|
||||
{
|
||||
iv ??= GenerateRandomIV(); // 生成随机 IV
|
||||
}
|
||||
// 验证密钥长度
|
||||
var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
|
||||
if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
|
||||
|
||||
using var aesAlg = Aes.Create();
|
||||
aesAlg.Key = bKey;
|
||||
@@ -138,34 +142,29 @@ public class AESEncryption
|
||||
|
||||
if (mode != CipherMode.ECB)
|
||||
{
|
||||
aesAlg.IV = iv;
|
||||
aesAlg.IV = iv ?? GenerateRandomIV();
|
||||
if (aesAlg.IV.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV), CryptoStreamMode.Write);
|
||||
|
||||
cryptoStream.Write(bytes, 0, bytes.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
|
||||
// 如果是 CBC 模式,将 IV 和密文拼接在一起
|
||||
if (mode != CipherMode.ECB)
|
||||
using (var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateEncryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
var result = new byte[aesAlg.IV.Length + memoryStream.ToArray().Length];
|
||||
cryptoStream.Write(bytes, 0, bytes.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
}
|
||||
|
||||
var encryptedContent = memoryStream.ToArray();
|
||||
|
||||
// 仅在未提供 IV 时拼接 IV
|
||||
if (mode != CipherMode.ECB && iv == null)
|
||||
{
|
||||
var result = new byte[aesAlg.IV.Length + encryptedContent.Length];
|
||||
Buffer.BlockCopy(aesAlg.IV, 0, result, 0, aesAlg.IV.Length);
|
||||
Buffer.BlockCopy(memoryStream.ToArray(), 0, result, aesAlg.IV.Length, memoryStream.ToArray().Length);
|
||||
Buffer.BlockCopy(encryptedContent, 0, result, aesAlg.IV.Length, encryptedContent.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 如果是 ECB 模式,直接返回密文
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// 生成随机 IV
|
||||
private static byte[] GenerateRandomIV()
|
||||
{
|
||||
using var aes = Aes.Create();
|
||||
aes.GenerateIV();
|
||||
return aes.IV;
|
||||
return encryptedContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,25 +175,13 @@ public class AESEncryption
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] Decrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static byte[] Decrypt(byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
// 确保密钥长度为 128 位、192 位或 256 位
|
||||
var bKey = new byte[32]; // 256 位密钥
|
||||
var keyBytes = Encoding.UTF8.GetBytes(skey);
|
||||
Array.Copy(keyBytes, bKey, Math.Min(keyBytes.Length, bKey.Length));
|
||||
|
||||
// 如果是 ECB 模式,不需要 IV
|
||||
if (mode != CipherMode.ECB)
|
||||
{
|
||||
if (iv == null)
|
||||
{
|
||||
// 从密文中提取 IV
|
||||
iv = new byte[16];
|
||||
Array.Copy(bytes, iv, iv.Length);
|
||||
bytes = bytes.Skip(iv.Length).ToArray();
|
||||
}
|
||||
}
|
||||
// 验证密钥长度
|
||||
var bKey = !isBase64 ? Encoding.UTF8.GetBytes(skey) : Convert.FromBase64String(skey);
|
||||
if (bKey.Length != 16 && bKey.Length != 24 && bKey.Length != 32) throw new ArgumentException("The key length must be 16, 24, or 32 bytes.");
|
||||
|
||||
using var aesAlg = Aes.Create();
|
||||
aesAlg.Key = bKey;
|
||||
@@ -203,21 +190,36 @@ public class AESEncryption
|
||||
|
||||
if (mode != CipherMode.ECB)
|
||||
{
|
||||
if (iv == null)
|
||||
{
|
||||
// 提取IV
|
||||
if (bytes.Length < 16) throw new ArgumentException("The ciphertext length is insufficient to extract the IV.");
|
||||
iv = bytes.Take(16).ToArray();
|
||||
bytes = bytes.Skip(16).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iv.Length != 16) throw new ArgumentException("The IV length must be 16 bytes.");
|
||||
}
|
||||
aesAlg.IV = iv;
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream(bytes);
|
||||
using var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV), CryptoStreamMode.Read);
|
||||
using var cryptoStream = new CryptoStream(memoryStream, aesAlg.CreateDecryptor(), CryptoStreamMode.Read);
|
||||
using var originalStream = new MemoryStream();
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var readBytes = 0;
|
||||
|
||||
while ((readBytes = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
originalStream.Write(buffer, 0, readBytes);
|
||||
}
|
||||
|
||||
cryptoStream.CopyTo(originalStream);
|
||||
return originalStream.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成随机 IV
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static byte[] GenerateRandomIV()
|
||||
{
|
||||
using var aes = Aes.Create();
|
||||
aes.GenerateIV();
|
||||
return aes.IV;
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.DataEncryption;
|
||||
|
||||
/// <summary>
|
||||
/// GZip 压缩解压
|
||||
/// </summary>
|
||||
[SuppressSniffer]
|
||||
public static class GzipEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// 压缩字符串并返回字节数组
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] Compress(string text)
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes(text);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
|
||||
{
|
||||
zip.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字节数组解压
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static string Decompress(byte[] bytes)
|
||||
{
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var zip = new GZipStream(ms, CompressionMode.Decompress);
|
||||
using var outStream = new MemoryStream();
|
||||
|
||||
zip.CopyTo(outStream);
|
||||
|
||||
return Encoding.UTF8.GetString(outStream.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 压缩字符串并返回 Base64 字符串
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string CompressToBase64(string text)
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes(text);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
|
||||
{
|
||||
zip.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(ms.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 Base64 字符串解压
|
||||
/// </summary>
|
||||
/// <param name="base64String"></param>
|
||||
/// <returns></returns>
|
||||
public static string DecompressFromBase64(string base64String)
|
||||
{
|
||||
var compressedData = Convert.FromBase64String(base64String);
|
||||
|
||||
using var ms = new MemoryStream(compressedData);
|
||||
using var zip = new GZipStream(ms, CompressionMode.Decompress);
|
||||
using var outStream = new MemoryStream();
|
||||
|
||||
zip.CopyTo(outStream);
|
||||
|
||||
return Encoding.UTF8.GetString(outStream.ToArray());
|
||||
}
|
||||
}
|
@@ -77,10 +77,11 @@ public static class StringEncryptionExtensions
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns>string</returns>
|
||||
public static string ToAESEncrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static string ToAESEncrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
return AESEncryption.Encrypt(text, skey, iv, mode, padding);
|
||||
return AESEncryption.Encrypt(text, skey, iv, mode, padding, isBase64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,10 +92,11 @@ public static class StringEncryptionExtensions
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns>string</returns>
|
||||
public static string ToAESDecrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static string ToAESDecrypt(this string text, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
return AESEncryption.Decrypt(text, skey, iv, mode, padding);
|
||||
return AESEncryption.Decrypt(text, skey, iv, mode, padding, isBase64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,10 +107,11 @@ public static class StringEncryptionExtensions
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns>string</returns>
|
||||
public static byte[] ToAESEncrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static byte[] ToAESEncrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
return AESEncryption.Encrypt(bytes, skey, iv, mode, padding);
|
||||
return AESEncryption.Encrypt(bytes, skey, iv, mode, padding, isBase64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,10 +122,11 @@ public static class StringEncryptionExtensions
|
||||
/// <param name="iv">偏移量</param>
|
||||
/// <param name="mode">模式</param>
|
||||
/// <param name="padding">填充</param>
|
||||
/// <param name="isBase64"></param>
|
||||
/// <returns>string</returns>
|
||||
public static byte[] ToAESDecrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
public static byte[] ToAESDecrypt(this byte[] bytes, string skey, byte[] iv = null, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7, bool isBase64 = false)
|
||||
{
|
||||
return AESEncryption.Decrypt(bytes, skey, iv, mode, padding);
|
||||
return AESEncryption.Decrypt(bytes, skey, iv, mode, padding, isBase64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -243,4 +247,44 @@ public static class StringEncryptionExtensions
|
||||
{
|
||||
return PBKDF2Encryption.Compare(text, hash, saltSize, iterationCount, derivedKeyLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gzip 压缩字符串并返回字节数组
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ToGzipCompress(this string text)
|
||||
{
|
||||
return GzipEncryption.Compress(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gzip 从字节数组解压
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToGzipDecompress(this byte[] bytes)
|
||||
{
|
||||
return GzipEncryption.Decompress(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gzip 压缩字符串并返回 Base64 字符串
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToGzipCompressToBase64(this string text)
|
||||
{
|
||||
return GzipEncryption.CompressToBase64(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gzip 从 Base64 字符串解压
|
||||
/// </summary>
|
||||
/// <param name="base64String"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToGzipDecompressFromBase64(this string base64String)
|
||||
{
|
||||
return GzipEncryption.DecompressFromBase64(base64String);
|
||||
}
|
||||
}
|
@@ -565,10 +565,10 @@ internal sealed class DynamicApiControllerApplicationModelConvention : IApplicat
|
||||
if (isLowerCamelCase) parameterModel.ParameterName = parameterModel.ParameterName.ToLowerCamelCase();
|
||||
|
||||
// 判断是否贴有任何 [FromXXX] 特性了
|
||||
var hasFormAttribute = parameterAttributes.Any(u => typeof(IBindingSourceMetadata).IsAssignableFrom(u.GetType()));
|
||||
var hasFromAttribute = parameterAttributes.Any(u => typeof(IBindingSourceMetadata).IsAssignableFrom(u.GetType()));
|
||||
|
||||
// 判断方法贴有 [QueryParameters] 特性且当前参数没有任何 [FromXXX] 特性,则添加 [FromQuery] 特性
|
||||
if (isQueryParametersAction && !hasFormAttribute)
|
||||
if (isQueryParametersAction && !hasFromAttribute)
|
||||
{
|
||||
parameterModel.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromQueryAttribute() });
|
||||
continue;
|
||||
@@ -577,7 +577,7 @@ internal sealed class DynamicApiControllerApplicationModelConvention : IApplicat
|
||||
// 如果没有贴 [FromRoute] 特性且不是基元类型,则跳过
|
||||
// 如果没有贴 [FromRoute] 特性且有任何绑定特性,则跳过
|
||||
if (!parameterAttributes.Any(u => u is FromRouteAttribute)
|
||||
&& (!parameterType.IsRichPrimitive() || hasFormAttribute)) continue;
|
||||
&& (!parameterType.IsRichPrimitive() || hasFromAttribute)) continue;
|
||||
|
||||
// 处理基元数组数组类型,还有全局配置参数问题
|
||||
if (_dynamicApiControllerSettings?.UrlParameterization == true || parameterType.IsArray)
|
||||
@@ -588,7 +588,7 @@ internal sealed class DynamicApiControllerApplicationModelConvention : IApplicat
|
||||
|
||||
// 处理 [ApiController] 特性情况
|
||||
// https://docs.microsoft.com/en-US/aspnet/core/web-api/?view=aspnetcore-5.0#binding-source-parameter-inference
|
||||
if (!hasFormAttribute && hasApiControllerAttribute) continue;
|
||||
if (!hasFromAttribute && hasApiControllerAttribute) continue;
|
||||
|
||||
// 处理默认基元参数绑定方式,若是 query([FromQuery])则跳过
|
||||
if (_dynamicApiControllerSettings?.DefaultBindingInfo?.ToLower() == "query")
|
||||
|
@@ -84,8 +84,6 @@ internal sealed class DynamicApiRuntimeChangeProvider : IDynamicApiRuntimeChange
|
||||
if (applicationPart != null) _applicationPartManager.ApplicationParts.Remove(applicationPart);
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -72,7 +72,7 @@ public sealed class EventBusOptionsBuilder
|
||||
/// <summary>
|
||||
/// 是否启用执行完成触发 GC 回收
|
||||
/// </summary>
|
||||
public bool GCCollect { get; set; } = true;
|
||||
public bool GCCollect { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用日志记录
|
||||
|
@@ -10,6 +10,7 @@
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ThingsGateway.EventBus;
|
||||
|
||||
@@ -57,4 +58,31 @@ public abstract class EventHandlerContext
|
||||
/// </summary>
|
||||
/// <remarks><remarks>如果是动态订阅,可能为 null</remarks></remarks>
|
||||
public EventSubscribeAttribute Attribute { get; }
|
||||
|
||||
private static JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default)
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
/// <summary>
|
||||
/// 获取负载数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T GetPayload<T>()
|
||||
{
|
||||
var rawPayload = Source.Payload;
|
||||
|
||||
if (rawPayload is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
else if (rawPayload is JsonElement jsonElement)
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(jsonElement.GetRawText(), JsonSerializerOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (T)rawPayload;
|
||||
}
|
||||
}
|
||||
}
|
@@ -38,4 +38,18 @@ public sealed class EventHandlerExecutingContext : EventHandlerContext
|
||||
/// 执行前时间
|
||||
/// </summary>
|
||||
public DateTime ExecutingTime { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行结果
|
||||
/// </summary>
|
||||
internal object Result { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置执行结果
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
public void SetResult(object result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}
|
@@ -42,4 +42,10 @@ public sealed class EventHandlerEventArgs : EventArgs
|
||||
/// 异常信息
|
||||
/// </summary>
|
||||
public Exception Exception { get; internal set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行结果
|
||||
/// </summary>
|
||||
public object Result { get; internal set; }
|
||||
}
|
@@ -304,7 +304,10 @@ internal sealed class EventBusHostedService : BackgroundService
|
||||
}
|
||||
|
||||
// 触发事件处理程序事件
|
||||
_eventPublisher.InvokeEvents(new(eventSource, true));
|
||||
_eventPublisher.InvokeEvents(new(eventSource, true)
|
||||
{
|
||||
Result = eventHandlerExecutingContext.Result
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@@ -198,8 +198,9 @@ public class JWTEncryption
|
||||
/// <param name="refreshTokenExpiredTime">新刷新 Token 有效期(分钟)</param>
|
||||
/// <param name="tokenPrefix"></param>
|
||||
/// <param name="clockSkew"></param>
|
||||
/// <param name="onRefreshing">当刷新时触发</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<bool> AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5)
|
||||
public static async Task<bool> AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5, Action<string, string> onRefreshing = null)
|
||||
{
|
||||
// 如果验证有效,则跳过刷新
|
||||
if (context.User.Identity.IsAuthenticated)
|
||||
@@ -245,7 +246,11 @@ public class JWTEncryption
|
||||
// 返回新的 Token
|
||||
httpContext.Response.Headers[accessTokenKey] = accessToken;
|
||||
// 返回新的 刷新Token
|
||||
httpContext.Response.Headers[xAccessTokenKey] = GenerateRefreshToken(accessToken, refreshTokenExpiredTime);
|
||||
var refreshAccessToken = GenerateRefreshToken(accessToken, refreshTokenExpiredTime); ;
|
||||
httpContext.Response.Headers[xAccessTokenKey] = refreshAccessToken;
|
||||
|
||||
// 调用刷新后回调函数
|
||||
onRefreshing?.Invoke(accessToken, refreshAccessToken);
|
||||
|
||||
// 处理 axios 问题
|
||||
httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs);
|
||||
|
@@ -90,7 +90,8 @@ public sealed class ConsoleFormatterExtend : ConsoleFormatter, IDisposable
|
||||
, true
|
||||
, _disableColors
|
||||
, _formatterOptions.WithTraceId
|
||||
, _formatterOptions.WithStackFrame);
|
||||
, _formatterOptions.WithStackFrame
|
||||
, _formatterOptions.FormatProvider);
|
||||
}
|
||||
|
||||
// 判断是否自定义了日志筛选器,如果是则检查是否符合条件
|
||||
|
@@ -12,6 +12,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace ThingsGateway.Logging;
|
||||
|
||||
/// <summary>
|
||||
@@ -69,4 +71,10 @@ public sealed class ConsoleFormatterExtendOptions : ConsoleFormatterOptions
|
||||
/// 日志消息内容转换(如脱敏处理)
|
||||
/// </summary>
|
||||
public Func<string, string> MessageProcess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化提供器
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
|
||||
}
|
@@ -118,7 +118,7 @@ public sealed class DatabaseLogger : ILogger
|
||||
// 设置日志消息模板
|
||||
logMsg.Message = _options.MessageFormat != null
|
||||
? _options.MessageFormat(logMsg)
|
||||
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame);
|
||||
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
|
||||
|
||||
// 空检查
|
||||
if (logMsg.Message is null)
|
||||
|
@@ -11,6 +11,8 @@
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace ThingsGateway.Logging;
|
||||
|
||||
/// <summary>
|
||||
@@ -80,4 +82,10 @@ public sealed class DatabaseLoggerOptions
|
||||
/// 日志消息内容转换(如脱敏处理)
|
||||
/// </summary>
|
||||
public Func<string, string> MessageProcess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化提供器
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
|
||||
}
|
@@ -116,7 +116,7 @@ public sealed class FileLogger : ILogger
|
||||
// 设置日志消息模板
|
||||
logMsg.Message = _options.MessageFormat != null
|
||||
? _options.MessageFormat(logMsg)
|
||||
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame);
|
||||
: Penetrates.OutputStandardMessage(logMsg, _options.DateFormat, withTraceId: _options.WithTraceId, withStackFrame: _options.WithStackFrame, provider: _options.FormatProvider);
|
||||
|
||||
// 空检查
|
||||
if (logMsg.Message is null)
|
||||
|
@@ -11,6 +11,8 @@
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace ThingsGateway.Logging;
|
||||
|
||||
/// <summary>
|
||||
@@ -104,4 +106,10 @@ public sealed class FileLoggerOptions
|
||||
/// 日志消息内容转换(如脱敏处理)
|
||||
/// </summary>
|
||||
public Func<string, string> MessageProcess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化提供器
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
|
||||
}
|
@@ -11,6 +11,8 @@
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace ThingsGateway.Logging;
|
||||
|
||||
/// <summary>
|
||||
@@ -120,6 +122,6 @@ public struct LogMessage
|
||||
/// <returns><see cref="string"/></returns>
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return Penetrates.OutputStandardMessage(this);
|
||||
return Penetrates.OutputStandardMessage(this, provider: CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
@@ -192,7 +192,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
/// <param name="claimsPrincipal"></param>
|
||||
/// <param name="authorization"></param>
|
||||
/// <returns></returns>
|
||||
private static List<string> GenerateAuthorizationTemplate(Utf8JsonWriter writer, ClaimsPrincipal claimsPrincipal, StringValues authorization)
|
||||
private List<string> GenerateAuthorizationTemplate(Utf8JsonWriter writer, ClaimsPrincipal claimsPrincipal, StringValues authorization)
|
||||
{
|
||||
var templates = new List<string>();
|
||||
|
||||
@@ -219,7 +219,7 @@ public sealed class LoggingMonitorAttribute : Attribute, IAsyncActionFilter, IAs
|
||||
var succeed = long.TryParse(value, out var seconds);
|
||||
if (succeed)
|
||||
{
|
||||
value = $"{value} ({DateTimeOffset.FromUnixTimeSeconds(seconds).ToLocalTime():yyyy-MM-dd HH:mm:ss:ffff(zzz) dddd} L)";
|
||||
value = $"{value} ({DateTimeOffset.FromUnixTimeSeconds(seconds).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:ffff(zzz) dddd", Settings.FormatProvider)} L)";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,6 +12,7 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -143,4 +144,11 @@ public sealed class LoggingMonitorSettings
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
SkipValidation = true
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 格式化提供器
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
public IFormatProvider? FormatProvider { get; set; } = CultureInfo.InvariantCulture;
|
||||
}
|
@@ -107,13 +107,15 @@ internal static class Penetrates
|
||||
/// <param name="isConsole"></param>
|
||||
/// <param name="withTraceId"></param>
|
||||
/// <param name="withStackFrame"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
internal static string OutputStandardMessage(LogMessage logMsg
|
||||
, string dateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd"
|
||||
, bool isConsole = false
|
||||
, bool disableColors = true
|
||||
, bool withTraceId = false
|
||||
, bool withStackFrame = false)
|
||||
, bool withStackFrame = false
|
||||
, IFormatProvider? provider = null)
|
||||
{
|
||||
// 空检查
|
||||
if (logMsg.Message is null) return null;
|
||||
@@ -127,7 +129,7 @@ internal static class Penetrates
|
||||
|
||||
_ = AppendWithColor(formatString, GetLogLevelString(logMsg.LogLevel), logLevelColors);
|
||||
formatString.Append(": ");
|
||||
formatString.Append(logMsg.LogDateTime.ToString(dateFormat));
|
||||
formatString.Append(logMsg.LogDateTime.ToString(dateFormat, provider));
|
||||
formatString.Append(' ');
|
||||
formatString.Append(logMsg.UseUtcTimestamp ? "U" : "L");
|
||||
formatString.Append(' ');
|
||||
|
@@ -78,9 +78,9 @@ public partial interface ISchedulerFactory
|
||||
/// <returns><see cref="IJob"/></returns>
|
||||
IJob CreateJob(IServiceProvider serviceProvider, JobFactoryContext context);
|
||||
|
||||
/// <summary>
|
||||
/// GC 垃圾回收器回收处理
|
||||
/// </summary>
|
||||
/// <remarks>避免频繁 GC 回收</remarks>
|
||||
void GCCollect();
|
||||
///// <summary>
|
||||
///// GC 垃圾回收器回收处理
|
||||
///// </summary>
|
||||
///// <remarks>避免频繁 GC 回收</remarks>
|
||||
//void GCCollect();
|
||||
}
|
@@ -183,9 +183,10 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
|
||||
// 标记当前方法初始化完成
|
||||
PreloadCompleted = true;
|
||||
|
||||
// 释放引用内存并立即回收GC
|
||||
// 释放引用内存
|
||||
_schedulerBuilders.Clear();
|
||||
GCCollect();
|
||||
|
||||
//GCCollect();
|
||||
|
||||
// 输出作业调度器初始化日志
|
||||
if (!preloadSucceed) _logger.LogWarning("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count);
|
||||
@@ -393,22 +394,22 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
|
||||
return jobHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GC 垃圾回收器回收处理
|
||||
/// </summary>
|
||||
/// <remarks>避免频繁 GC 回收</remarks>
|
||||
public void GCCollect()
|
||||
{
|
||||
var nowTime = DateTime.UtcNow;
|
||||
if ((LastGCCollectTime == null || (nowTime - LastGCCollectTime.Value).TotalMilliseconds > GC_COLLECT_INTERVAL_MILLISECONDS))
|
||||
{
|
||||
LastGCCollectTime = nowTime;
|
||||
///// <summary>
|
||||
///// GC 垃圾回收器回收处理
|
||||
///// </summary>
|
||||
///// <remarks>避免频繁 GC 回收</remarks>
|
||||
//public void GCCollect()
|
||||
//{
|
||||
// var nowTime = DateTime.UtcNow;
|
||||
// if ((LastGCCollectTime == null || (nowTime - LastGCCollectTime.Value).TotalMilliseconds > GC_COLLECT_INTERVAL_MILLISECONDS))
|
||||
// {
|
||||
// LastGCCollectTime = nowTime;
|
||||
|
||||
// 通知 GC 垃圾回收器立即回收
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
}
|
||||
// // 通知 GC 垃圾回收器立即回收
|
||||
// GC.Collect();
|
||||
// GC.WaitForPendingFinalizers();
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 释放非托管资源
|
||||
@@ -535,7 +536,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
|
||||
//_logger.LogWarning("Schedule hosted service cancels hibernation.");
|
||||
|
||||
// 通知 GC 垃圾回收器立即回收
|
||||
GCCollect();
|
||||
//GCCollect();
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -389,7 +389,7 @@ internal sealed class ScheduleHostedService : BackgroundService
|
||||
_jobCancellationToken.Cancel(jobId, triggerId, false);
|
||||
|
||||
// 通知 GC 垃圾回收器回收
|
||||
_schedulerFactory.GCCollect();
|
||||
//_schedulerFactory.GCCollect();
|
||||
}
|
||||
}, stoppingToken);
|
||||
});
|
||||
|
@@ -113,10 +113,8 @@ public static class SpecificationDocumentBuilder
|
||||
}
|
||||
|
||||
// 处理贴有 [ApiExplorerSettings(IgnoreApi = true)] 或者 [ApiDescriptionSettings(false)] 特性的接口
|
||||
var apiExplorerSettings = method.GetFoundAttribute<ApiExplorerSettingsAttribute>(true);
|
||||
|
||||
var apiDescriptionSettings = method.GetFoundAttribute<ApiDescriptionSettingsAttribute>(true);
|
||||
|
||||
var apiExplorerSettings = method.GetFoundAttribute<ApiExplorerSettingsAttribute>(true, true);
|
||||
var apiDescriptionSettings = method.GetFoundAttribute<ApiDescriptionSettingsAttribute>(true, true);
|
||||
if (apiExplorerSettings?.IgnoreApi == true || apiDescriptionSettings?.IgnoreApi == true) return false;
|
||||
|
||||
if (currentGroup == AllGroupsKey)
|
||||
|
@@ -433,10 +433,15 @@ public partial class Crontab
|
||||
{
|
||||
newValue = newValue.AddSeconds(-newValue.Second);
|
||||
}
|
||||
|
||||
// 初始化是否存在随机 R 标识符
|
||||
var randomSecond = false;
|
||||
var randomMinute = false;
|
||||
var randomHour = false;
|
||||
// 获取分钟、小时所有字符解析器
|
||||
var minuteParsers = Parsers[CrontabFieldKind.Minute].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
|
||||
randomMinute = minuteParsers.OfType<RandomParser>().Any();
|
||||
var hourParsers = Parsers[CrontabFieldKind.Hour].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
|
||||
randomHour = hourParsers.OfType<RandomParser>().Any();
|
||||
|
||||
// 获取秒、分钟、小时解析器中最小起始值
|
||||
// 该值主要用来获取下一个发生值的输入参数
|
||||
@@ -456,7 +461,7 @@ public partial class Crontab
|
||||
{
|
||||
// 获取秒所有字符解析器
|
||||
var secondParsers = Parsers[CrontabFieldKind.Second].Where(x => x is ITimeParser).Cast<ITimeParser>().ToList();
|
||||
|
||||
randomSecond = secondParsers.OfType<RandomParser>().Any();
|
||||
// 获取秒解析器最小起始值
|
||||
firstSecondValue = secondParsers.Select(x => x.First()).Min();
|
||||
|
||||
@@ -519,8 +524,8 @@ public partial class Crontab
|
||||
|
||||
// 设置起始时间为下一个小时时间
|
||||
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours,
|
||||
overflow ? firstMinuteValue : newMinutes,
|
||||
overflow ? firstSecondValue : newSeconds);
|
||||
overflow && !randomMinute ? firstMinuteValue : newMinutes,
|
||||
overflow && !randomSecond ? firstSecondValue : newSeconds);
|
||||
|
||||
// 如果当前小时并没有进入下一轮循环但存在不匹配的字符过滤器
|
||||
if (!overflow && !IsMatch(newValue))
|
||||
@@ -534,7 +539,7 @@ public partial class Crontab
|
||||
}
|
||||
|
||||
// 如果程序到达这里,说明并没有进入上面分支,则直接返回下一小时时间
|
||||
if (!overflow)
|
||||
if (!randomHour && !overflow)
|
||||
{
|
||||
return MinDate(newValue, endTime);
|
||||
}
|
||||
@@ -788,8 +793,15 @@ public partial class Crontab
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param>
|
||||
/// <returns><see cref="int"/></returns>
|
||||
private static int Increment(IEnumerable<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
|
||||
private static int Increment(List<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
|
||||
{
|
||||
// 检查是否是随机 R 字符解析器
|
||||
if (parsers.Count == 1 && parsers.First() is RandomParser randomParser)
|
||||
{
|
||||
overflow = true;
|
||||
return randomParser.Next(value).Value;
|
||||
}
|
||||
|
||||
var nextValue = parsers.Select(x => x.Next(value))
|
||||
.Where(x => x > value)
|
||||
.Min()
|
||||
@@ -808,7 +820,7 @@ public partial class Crontab
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <param name="overflow">控制秒、分钟、小时到达59秒/分和23小时开关</param>
|
||||
/// <returns><see cref="int"/></returns>
|
||||
private static int Decrement(IEnumerable<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
|
||||
private static int Decrement(List<ITimeParser> parsers, int value, int defaultValue, out bool overflow)
|
||||
{
|
||||
var previousValue = parsers.Select(x => x.Previous(value))
|
||||
.Where(x => x < value)
|
||||
|
@@ -69,7 +69,7 @@ internal sealed class RandomParser : ICronParser, ITimeParser
|
||||
/// <returns><see cref="bool"/></returns>
|
||||
public bool IsMatch(DateTime datetime)
|
||||
{
|
||||
return true;
|
||||
return Kind is not CrontabFieldKind.Hour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -168,7 +168,7 @@ public static class UnifyContext
|
||||
if (context.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) return null;
|
||||
|
||||
// 获取序列化配置
|
||||
var unifySerializerSettingAttribute = actionDescriptor.MethodInfo.GetFoundAttribute<UnifySerializerSettingAttribute>(true);
|
||||
var unifySerializerSettingAttribute = actionDescriptor.MethodInfo.GetFoundAttribute<UnifySerializerSettingAttribute>(true, true);
|
||||
if (unifySerializerSettingAttribute == null || string.IsNullOrWhiteSpace(unifySerializerSettingAttribute.Name)) return null;
|
||||
|
||||
// 解析全局配置
|
||||
@@ -225,7 +225,8 @@ public static class UnifyContext
|
||||
|| method.GetRealReturnType().HasImplementedRawGeneric(unityMetadata.ResultType)
|
||||
|| method.CustomAttributes.Any(x => typeof(NonUnifyAttribute).IsAssignableFrom(x.AttributeType) || typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|
||||
|| method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData");
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData")
|
||||
|| method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>);
|
||||
|
||||
if (!isWebRequest)
|
||||
{
|
||||
@@ -255,7 +256,8 @@ public static class UnifyContext
|
||||
!method.CustomAttributes.Any(x => typeof(ProducesResponseTypeAttribute).IsAssignableFrom(x.AttributeType) || typeof(IApiResponseMetadataProvider).IsAssignableFrom(x.AttributeType))
|
||||
&& method.ReflectedType.IsDefined(typeof(NonUnifyAttribute), true)
|
||||
)
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData");
|
||||
|| method.DeclaringType.Assembly.GetName().Name.StartsWith("Microsoft.AspNetCore.OData")
|
||||
|| method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>);
|
||||
|
||||
unifyResult = isSkip ? null : App.RootServices.GetService(unityMetadata.ProviderType) as IUnifyResultProvider;
|
||||
return unifyResult == null || isSkip;
|
||||
@@ -398,7 +400,7 @@ public static class UnifyContext
|
||||
{
|
||||
if (method == default) return default;
|
||||
|
||||
var unityProviderAttribute = method.GetFoundAttribute<UnifyProviderAttribute>(true);
|
||||
var unityProviderAttribute = method.GetFoundAttribute<UnifyProviderAttribute>(true, true);
|
||||
|
||||
// 获取元数据
|
||||
var isExists = UnifyProviders.TryGetValue(unityProviderAttribute?.Name ?? string.Empty, out var metadata);
|
||||
|
@@ -0,0 +1,38 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ThingsGateway.Converters.Json;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DateTime" /> JSON 序列化转换器
|
||||
/// </summary>
|
||||
/// <remarks>在不符合 <c>ISO 8601-1:2019</c> 格式的 <see cref="DateTime" /> 时间使用 <c>DateTime.Parse</c> 作为回退。</remarks>
|
||||
public sealed class DateTimeConverterUsingDateTimeParseAsFallback : JsonConverter<DateTime>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// 尝试获取 ISO 8601-1:2019 格式时间
|
||||
if (!reader.TryGetDateTime(out var value))
|
||||
{
|
||||
value = DateTime.Parse(reader.GetString()!);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) =>
|
||||
JsonSerializer.Serialize(writer, value);
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ThingsGateway.Converters.Json;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DateTimeOffset" /> JSON 序列化转换器
|
||||
/// </summary>
|
||||
/// <remarks>在不符合 <c>ISO 8601-1:2019</c> 格式的 <see cref="DateTimeOffset" /> 时间使用 <c>DateTimeOffset.Parse</c> 作为回退。</remarks>
|
||||
public sealed class DateTimeOffsetConverterUsingDateTimeOffsetParseAsFallback : JsonConverter<DateTimeOffset>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// 尝试获取 ISO 8601-1:2019 格式时间
|
||||
if (!reader.TryGetDateTimeOffset(out var value))
|
||||
{
|
||||
value = DateTimeOffset.Parse(reader.GetString()!);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) =>
|
||||
JsonSerializer.Serialize(writer, value);
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
|
||||
namespace ThingsGateway.Converters.Json;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="string" /> JSON 序列化转换器
|
||||
/// </summary>
|
||||
/// <remarks>解决 Number 类型和 Boolean 类型转 String 类型时异常。</remarks>
|
||||
public sealed class StringJsonConverter : JsonConverter<string>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.True or JsonTokenType.False => reader.GetBoolean().ToString(),
|
||||
JsonTokenType.Number => reader.ConvertRawValueToString(),
|
||||
_ => reader.GetString()
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
|
||||
writer.WriteStringValue(value);
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ThingsGateway.Extensions;
|
||||
|
||||
@@ -43,7 +44,7 @@ internal static class LinqExpressionExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析表达式属性名称
|
||||
/// 解析表达式并获取属性的 <see cref="PropertyInfo" /> 实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <typeparam name="TProperty">属性类型</typeparam>
|
||||
@@ -51,48 +52,54 @@ internal static class LinqExpressionExtensions
|
||||
/// <see cref="Expression{TDelegate}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="string" />
|
||||
/// <see cref="PropertyInfo" />
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
internal static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty?>> propertySelector) =>
|
||||
internal static PropertyInfo GetProperty<T, TProperty>(this Expression<Func<T, TProperty?>> propertySelector) =>
|
||||
propertySelector.Body switch
|
||||
{
|
||||
// 检查 Lambda 表达式的主体是否是 MemberExpression 类型
|
||||
MemberExpression memberExpression => GetPropertyName<T>(memberExpression),
|
||||
|
||||
MemberExpression memberExpression => GetProperty<T>(memberExpression),
|
||||
// 如果主体是 UnaryExpression 类型,则继续解析
|
||||
UnaryExpression { Operand: MemberExpression nestedMemberExpression } => GetPropertyName<T>(
|
||||
UnaryExpression { Operand: MemberExpression nestedMemberExpression } => GetProperty<T>(
|
||||
nestedMemberExpression),
|
||||
|
||||
_ => throw new ArgumentException("Expression is not valid for property selection.")
|
||||
_ => throw new ArgumentException("Expression must be a simple member access (e.g. x => x.Property).",
|
||||
nameof(propertySelector))
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 解析表达式属性名称
|
||||
/// 从成员表达式中提取 <see cref="PropertyInfo" /> 实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <param name="memberExpression">
|
||||
/// <see cref="MemberExpression" />
|
||||
/// </param>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <returns>
|
||||
/// <see cref="string" />
|
||||
/// <see cref="PropertyInfo" />
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
internal static string GetPropertyName<T>(MemberExpression memberExpression)
|
||||
internal static PropertyInfo GetProperty<T>(MemberExpression memberExpression)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(memberExpression);
|
||||
|
||||
// 获取属性声明类型
|
||||
var propertyType = memberExpression.Member.DeclaringType;
|
||||
|
||||
// 检查是否越界访问属性
|
||||
if (propertyType != typeof(T))
|
||||
// 确保表达式根是 T 类型的参数
|
||||
if (memberExpression.Expression is not ParameterExpression parameterExpression ||
|
||||
parameterExpression.Type != typeof(T))
|
||||
{
|
||||
throw new ArgumentException("Invalid property selection.");
|
||||
throw new ArgumentException(
|
||||
$"Expression '{memberExpression}' must refer to a member of type '{typeof(T)}'.",
|
||||
nameof(memberExpression));
|
||||
}
|
||||
|
||||
// 返回属性名称
|
||||
return memberExpression.Member.Name;
|
||||
// 确保成员是属性(非字段)
|
||||
if (memberExpression.Member is not PropertyInfo propertyInfo)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Expression '{memberExpression}' refers to a field. Only properties are supported.",
|
||||
nameof(memberExpression));
|
||||
}
|
||||
|
||||
return propertyInfo;
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
@@ -149,7 +150,7 @@ internal static partial class StringExtensions
|
||||
|
||||
var pairs = (trimChar is null ? keyValueString : keyValueString.TrimStart(trimChar.Value)).Split(separators);
|
||||
return (from pair in pairs
|
||||
select pair.Split('=')
|
||||
select pair.Split('=', 2) // 限制只分割一次
|
||||
into keyValue
|
||||
where keyValue.Length == 2
|
||||
select new KeyValuePair<string, string?>(keyValue[0].Trim(), keyValue[1])).ToList();
|
||||
@@ -328,6 +329,18 @@ internal static partial class StringExtensions
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换输入字符串中的任何转义字符
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// <see cref="string" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="string" />
|
||||
/// </returns>
|
||||
internal static string? Unescape([NotNullIfNotNull(nameof(input))] this string? input) =>
|
||||
string.IsNullOrWhiteSpace(input) ? input : Regex.Unescape(input);
|
||||
|
||||
/// <summary>
|
||||
/// 占位符匹配正则表达式
|
||||
/// </summary>
|
||||
|
@@ -9,6 +9,8 @@
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ThingsGateway.Extensions;
|
||||
@@ -34,4 +36,17 @@ internal static class Utf8JsonReaderExtensions
|
||||
|
||||
return jsonDocument.RootElement.Clone().GetRawText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 <see cref="Utf8JsonReader" /> 中提取原始值,并将其转换为字符串
|
||||
/// </summary>
|
||||
/// <remarks>支持处理各种类型的原始值(例如数字、布尔值等)。</remarks>
|
||||
/// <param name="reader">
|
||||
/// <see cref="Utf8JsonReader" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="string" />
|
||||
/// </returns>
|
||||
internal static string ConvertRawValueToString(this Utf8JsonReader reader) =>
|
||||
Encoding.UTF8.GetString(reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan);
|
||||
}
|
@@ -97,16 +97,45 @@ internal static class V5_ObjectExtensions
|
||||
case ICollection collection:
|
||||
count = collection.Count;
|
||||
return true;
|
||||
// 检查对象是否实现了 IEnumerable 接口
|
||||
case IEnumerable enumerable:
|
||||
// 获取集合枚举数
|
||||
var enumerator = enumerable.GetEnumerator();
|
||||
|
||||
try
|
||||
{
|
||||
// 检查枚举数是否可以推进到下一个元素
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
count = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 枚举数循环推进到下一个元素并叠加推进次数
|
||||
var c = 1;
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
c++;
|
||||
}
|
||||
|
||||
count = c;
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 检查枚举数是否实现了 IDisposable 接口
|
||||
if (enumerator is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 反射查找是否存在 Count 属性
|
||||
var runtimeProperty = obj.GetType()
|
||||
.GetRuntimeProperty("Count");
|
||||
var runtimeProperty = obj.GetType().GetRuntimeProperty("Count");
|
||||
|
||||
// 反射获取 Count 属性值
|
||||
if (runtimeProperty is not null
|
||||
&& runtimeProperty.CanRead
|
||||
&& runtimeProperty.PropertyType == typeof(int))
|
||||
if (runtimeProperty is not null && runtimeProperty.CanRead && runtimeProperty.PropertyType == typeof(int))
|
||||
{
|
||||
count = (int)runtimeProperty.GetValue(obj)!;
|
||||
return true;
|
||||
|
@@ -38,7 +38,7 @@ public sealed class HttpContextForwardBuilder
|
||||
/// <summary>
|
||||
/// 忽略在转发时需要跳过的请求标头列表
|
||||
/// </summary>
|
||||
internal static HashSet<string> _ignoreRequestHeaders =
|
||||
internal static readonly HashSet<string> _ignoreRequestHeaders =
|
||||
[
|
||||
Constants.X_FORWARD_TO_HEADER, "Host", "Accept", "Accept-CH", "Accept-Charset", "Accept-Encoding",
|
||||
"Accept-Language", "Accept-Patch", "Accept-Post", "Accept-Ranges"
|
||||
@@ -356,8 +356,7 @@ public sealed class HttpContextForwardBuilder
|
||||
if (multipartSection.AsFileSection() is not null)
|
||||
{
|
||||
// 复制多部分表单内容文件节内容
|
||||
await CopyFileMultipartSectionAsync(multipartSection, httpMultipartFormDataBuilder, httpRequestBuilder,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
await CopyFileMultipartSectionAsync(multipartSection, httpMultipartFormDataBuilder, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -410,15 +409,11 @@ public sealed class HttpContextForwardBuilder
|
||||
/// <param name="httpMultipartFormDataBuilder">
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </param>
|
||||
/// <param name="httpRequestBuilder">
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">
|
||||
/// <see cref="CancellationToken" />
|
||||
/// </param>
|
||||
internal static async Task CopyFileMultipartSectionAsync(MultipartSection multipartSection,
|
||||
HttpMultipartFormDataBuilder httpMultipartFormDataBuilder, HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken)
|
||||
HttpMultipartFormDataBuilder httpMultipartFormDataBuilder, CancellationToken cancellationToken)
|
||||
{
|
||||
// 初始化 MemoryStream 实例
|
||||
var memoryStream = new MemoryStream();
|
||||
@@ -433,10 +428,8 @@ public sealed class HttpContextForwardBuilder
|
||||
var fileMultipartSection = multipartSection.AsFileSection()!;
|
||||
|
||||
// 添加文件流
|
||||
httpMultipartFormDataBuilder.AddStream(memoryStream, fileMultipartSection.Name, fileMultipartSection.FileName);
|
||||
|
||||
// 添加文件流到请求结束时需要释放的集合中
|
||||
httpRequestBuilder.AddDisposable(memoryStream);
|
||||
httpMultipartFormDataBuilder.AddStream(memoryStream, fileMultipartSection.Name, fileMultipartSection.FileName,
|
||||
disposeStreamOnRequestCompletion: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -124,12 +124,9 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </returns>
|
||||
/// <exception cref="JsonException"></exception>
|
||||
public HttpMultipartFormDataBuilder AddJson(object rawJson, string? name = null, Encoding? contentEncoding = null,
|
||||
public HttpMultipartFormDataBuilder AddJson(object? rawJson, string? name = null, Encoding? contentEncoding = null,
|
||||
string? contentType = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(rawJson);
|
||||
|
||||
// 检查是否配置表单名或不是字符串类型
|
||||
if (!string.IsNullOrWhiteSpace(name) || rawJson is not string rawString)
|
||||
{
|
||||
@@ -292,10 +289,8 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
// 从互联网 URL 地址中加载流
|
||||
var fileStream = Helpers.GetStreamFromRemote(url);
|
||||
|
||||
// 添加文件流到请求结束时需要释放的集合中
|
||||
_httpRequestBuilder.AddDisposable(fileStream);
|
||||
|
||||
return AddStream(fileStream, name, newFileName, contentType, contentEncoding);
|
||||
return AddStream(fileStream, name, newFileName, contentType, contentEncoding,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -365,10 +360,8 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
// 读取文件流(没有 using)
|
||||
var fileStream = File.OpenRead(filePath);
|
||||
|
||||
// 添加文件流到请求结束时需要释放的集合中
|
||||
_httpRequestBuilder.AddDisposable(fileStream);
|
||||
|
||||
return AddStream(fileStream, name, newFileName, contentType, contentEncoding);
|
||||
return AddStream(fileStream, name, newFileName, contentType, contentEncoding,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -407,10 +400,8 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
// 初始化带读写进度的文件流
|
||||
var progressFileStream = new ProgressFileStream(fileStream, filePath, progressChannel, newFileName);
|
||||
|
||||
// 添加文件流到请求结束时需要释放的集合中
|
||||
_httpRequestBuilder.AddDisposable(progressFileStream);
|
||||
|
||||
return AddStream(progressFileStream, name, newFileName, contentType, contentEncoding);
|
||||
return AddStream(progressFileStream, name, newFileName, contentType, contentEncoding,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -500,11 +491,12 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
/// <param name="fileName">文件的名称</param>
|
||||
/// <param name="contentType">内容类型</param>
|
||||
/// <param name="contentEncoding">内容编码</param>
|
||||
/// <param name="disposeStreamOnRequestCompletion">是否在请求结束后自动释放流。默认值为:<c>false</c></param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </returns>
|
||||
public HttpMultipartFormDataBuilder AddStream(Stream stream, string name = "file", string? fileName = null,
|
||||
string? contentType = null, Encoding? contentEncoding = null)
|
||||
string? contentType = null, Encoding? contentEncoding = null, bool disposeStreamOnRequestCompletion = false)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
@@ -529,6 +521,12 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
FileName = fileName
|
||||
});
|
||||
|
||||
// 是否在请求结束后自动释放流
|
||||
if (disposeStreamOnRequestCompletion)
|
||||
{
|
||||
_httpRequestBuilder.AddDisposable(stream);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -697,6 +695,20 @@ public sealed class HttpMultipartFormDataBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否移除默认的多部分内容的 <c>Content-Type</c>
|
||||
/// </summary>
|
||||
/// <param name="omit">如果为 <c>true</c> 则移除,默认为 <c>false</c></param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </returns>
|
||||
public HttpMultipartFormDataBuilder SetOmitContentType(bool omit)
|
||||
{
|
||||
OmitContentType = omit;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建 <see cref="MultipartFormDataContent" /> 实例
|
||||
/// </summary>
|
||||
|
@@ -327,23 +327,20 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="escape">是否转义字符串,默认 <c>false</c></param>
|
||||
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c></param>
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithHeader(string key, object? value, bool escape = false, CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null, bool replace = false)
|
||||
public HttpRequestBuilder WithHeader(string key, object? value, bool escape = false, bool replace = false,
|
||||
CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
||||
|
||||
return WithHeaders(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer, replace);
|
||||
return WithHeaders(new Dictionary<string, object?> { { key, value } }, escape, replace, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -352,25 +349,22 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <remarks>支持多次调用。</remarks>
|
||||
/// <param name="headers">请求标头集合</param>
|
||||
/// <param name="escape">是否转义字符串,默认 <c>false</c></param>
|
||||
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c></param>
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithHeaders(IDictionary<string, object?> headers, bool escape = false,
|
||||
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false)
|
||||
bool replace = false, CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(headers);
|
||||
|
||||
// 初始化请求标头
|
||||
Headers ??= new Dictionary<string, List<string?>>(comparer);
|
||||
var objectHeaders = new Dictionary<string, List<object?>>(comparer);
|
||||
Headers ??= new Dictionary<string, List<string?>>(StringComparer.OrdinalIgnoreCase);
|
||||
var objectHeaders = new Dictionary<string, List<object?>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 存在则合并否则添加
|
||||
objectHeaders.AddOrUpdate(Headers.ToDictionary(u => u.Key, object? (u) => u.Value), false);
|
||||
@@ -380,7 +374,7 @@ public sealed partial class HttpRequestBuilder
|
||||
Headers = objectHeaders.ToDictionary(kvp => kvp.Key,
|
||||
kvp => kvp.Value.Select(u =>
|
||||
u.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape)).ToList(),
|
||||
comparer);
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -391,26 +385,23 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <remarks>支持多次调用。</remarks>
|
||||
/// <param name="headerSource">请求标头源对象</param>
|
||||
/// <param name="escape">是否转义字符串,默认 <c>false</c></param>
|
||||
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c></param>
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <param name="replace">是否替换已存在的请求标头。默认值为 <c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithHeaders(object headerSource, bool escape = false, CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null, bool replace = false)
|
||||
public HttpRequestBuilder WithHeaders(object headerSource, bool escape = false, bool replace = false,
|
||||
CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(headerSource);
|
||||
|
||||
return WithHeaders(
|
||||
headerSource.ObjectToDictionary()!.ToDictionary(
|
||||
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, culture,
|
||||
comparer, replace);
|
||||
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, replace,
|
||||
culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -474,6 +465,7 @@ public sealed partial class HttpRequestBuilder
|
||||
public HttpRequestBuilder SetTimeout(TimeSpan timeout)
|
||||
{
|
||||
Timeout = timeout;
|
||||
TimeoutAction = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -494,6 +486,43 @@ public sealed partial class HttpRequestBuilder
|
||||
}
|
||||
|
||||
Timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
|
||||
TimeoutAction = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置超时时间
|
||||
/// </summary>
|
||||
/// <param name="timeout">超时时间</param>
|
||||
/// <param name="onTimeout">超时发生时要执行的操作</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SetTimeout(TimeSpan timeout, Action onTimeout)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(onTimeout);
|
||||
|
||||
SetTimeout(timeout).TimeoutAction = onTimeout;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置超时时间
|
||||
/// </summary>
|
||||
/// <param name="timeoutMilliseconds">超时时间(毫秒)</param>
|
||||
/// <param name="onTimeout">超时发生时要执行的操作</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SetTimeout(double timeoutMilliseconds, Action onTimeout)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(onTimeout);
|
||||
|
||||
SetTimeout(timeoutMilliseconds).TimeoutAction = onTimeout;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -570,26 +599,22 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="escape">是否转义字符串,默认 <c>false</c></param>
|
||||
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c></param>
|
||||
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c></param>
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c>。</param>
|
||||
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithQueryParameter(string key, object? value, bool escape = false,
|
||||
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false,
|
||||
bool ignoreNullValues = false)
|
||||
public HttpRequestBuilder WithQueryParameter(string key, object? value, bool escape = false, bool replace = false,
|
||||
bool ignoreNullValues = false, CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
||||
|
||||
return WithQueryParameters(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer,
|
||||
replace, ignoreNullValues);
|
||||
return WithQueryParameters(new Dictionary<string, object?> { { key, value } }, escape, replace,
|
||||
ignoreNullValues, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -598,27 +623,23 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <remarks>支持多次调用。</remarks>
|
||||
/// <param name="parameters">查询参数集合</param>
|
||||
/// <param name="escape">是否转义字符串,默认 <c>false</c></param>
|
||||
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c></param>
|
||||
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c></param>
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c>。</param>
|
||||
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithQueryParameters(IDictionary<string, object?> parameters, bool escape = false,
|
||||
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false,
|
||||
bool ignoreNullValues = false)
|
||||
bool replace = false, bool ignoreNullValues = false, CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(parameters);
|
||||
|
||||
// 初始化查询参数
|
||||
QueryParameters ??= new Dictionary<string, List<string?>>(comparer);
|
||||
var objectQueryParameters = new Dictionary<string, List<object?>>(comparer);
|
||||
QueryParameters ??= new Dictionary<string, List<string?>>(StringComparer.OrdinalIgnoreCase);
|
||||
var objectQueryParameters = new Dictionary<string, List<object?>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 存在则合并否则添加
|
||||
objectQueryParameters.AddOrUpdate(QueryParameters.ToDictionary(u => u.Key, object? (u) => u.Value), false);
|
||||
@@ -629,7 +650,7 @@ public sealed partial class HttpRequestBuilder
|
||||
QueryParameters = objectQueryParameters.ToDictionary(kvp => kvp.Key,
|
||||
kvp => kvp.Value.Select(u =>
|
||||
u.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape)).ToList(),
|
||||
comparer);
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -641,20 +662,16 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="parameterSource">查询参数集合</param>
|
||||
/// <param name="prefix">参数前缀。对于对象类型可生成如 <c>prefix.Name=furion</c> 与 <c>prefix.Age=30</c> 参数格式。</param>
|
||||
/// <param name="escape">是否转义字符串,默认 <c>false</c></param>
|
||||
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c></param>
|
||||
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c></param>
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <param name="replace">是否替换已存在的查询参数。默认值为 <c>false</c>。</param>
|
||||
/// <param name="ignoreNullValues">是否忽略空值。默认值为 <c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithQueryParameters(object parameterSource, string? prefix = null, bool escape = false,
|
||||
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null, bool replace = false,
|
||||
bool ignoreNullValues = false)
|
||||
bool replace = false, bool ignoreNullValues = false, CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(parameterSource);
|
||||
@@ -663,7 +680,7 @@ public sealed partial class HttpRequestBuilder
|
||||
parameterSource.ObjectToDictionary()!.ToDictionary(
|
||||
u =>
|
||||
$"{(string.IsNullOrWhiteSpace(prefix) ? null : $"{prefix}.")}{u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!}",
|
||||
u => u.Value), escape, culture, comparer, replace, ignoreNullValues);
|
||||
u => u.Value), escape, replace, ignoreNullValues, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -709,19 +726,16 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithPathParameter(string key, object? value, bool escape = false,
|
||||
CultureInfo? culture = null, IEqualityComparer<string>? comparer = null)
|
||||
CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
||||
|
||||
return WithPathParameters(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer);
|
||||
return WithPathParameters(new Dictionary<string, object?> { { key, value } }, escape, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -733,26 +747,21 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithPathParameters(IDictionary<string, object?> parameters,
|
||||
bool escape = false,
|
||||
CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null)
|
||||
public HttpRequestBuilder WithPathParameters(IDictionary<string, object?> parameters, bool escape = false,
|
||||
CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(parameters);
|
||||
|
||||
PathParameters ??= new Dictionary<string, string?>(comparer);
|
||||
PathParameters ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 存在则更新否则添加
|
||||
PathParameters.AddOrUpdate(parameters.ToDictionary(u => u.Key,
|
||||
u => u.Value?.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape),
|
||||
comparer));
|
||||
StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -767,15 +776,11 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithPathParameters(object? parameterSource, string? prefix = null, bool escape = false,
|
||||
CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null)
|
||||
CultureInfo? culture = null)
|
||||
{
|
||||
// 检查是否设置了模板字符串前缀
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
@@ -786,7 +791,7 @@ public sealed partial class HttpRequestBuilder
|
||||
return WithPathParameters(
|
||||
parameterSource.ObjectToDictionary()!.ToDictionary(
|
||||
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape,
|
||||
culture, comparer);
|
||||
culture);
|
||||
}
|
||||
|
||||
ObjectPathParameters ??= new Dictionary<string, object?>();
|
||||
@@ -823,19 +828,15 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithCookie(string key, object? value, bool escape = false, CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null)
|
||||
public HttpRequestBuilder WithCookie(string key, object? value, bool escape = false, CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
||||
|
||||
return WithCookies(new Dictionary<string, object?> { { key, value } }, escape, culture, comparer);
|
||||
return WithCookies(new Dictionary<string, object?> { { key, value } }, escape, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -847,26 +848,21 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithCookies(IDictionary<string, object?> cookies,
|
||||
bool escape = false,
|
||||
CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null)
|
||||
public HttpRequestBuilder WithCookies(IDictionary<string, object?> cookies, bool escape = false,
|
||||
CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(cookies);
|
||||
|
||||
Cookies ??= new Dictionary<string, string?>(comparer);
|
||||
Cookies ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 存在则更新否则添加
|
||||
Cookies.AddOrUpdate(cookies.ToDictionary(u => u.Key,
|
||||
u => u.Value?.ToCultureString(culture ?? CultureInfo.InvariantCulture)?.EscapeDataString(escape),
|
||||
comparer));
|
||||
StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -880,15 +876,10 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <param name="culture">
|
||||
/// <see cref="CultureInfo" />
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// <see cref="IEqualityComparer{T}" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder WithCookies(object cookieSource, bool escape = false,
|
||||
CultureInfo? culture = null,
|
||||
IEqualityComparer<string>? comparer = null)
|
||||
public HttpRequestBuilder WithCookies(object cookieSource, bool escape = false, CultureInfo? culture = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(cookieSource);
|
||||
@@ -896,8 +887,7 @@ public sealed partial class HttpRequestBuilder
|
||||
// 存在则更新否则添加
|
||||
return WithCookies(
|
||||
cookieSource.ObjectToDictionary()!.ToDictionary(
|
||||
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, culture,
|
||||
comparer);
|
||||
u => u.Key.ToCultureString(culture ?? CultureInfo.InvariantCulture)!, u => u.Value), escape, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1193,6 +1183,17 @@ public sealed partial class HttpRequestBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置身份验证凭据请求授权标头
|
||||
/// </summary>
|
||||
/// <param name="scheme">身份验证的方案</param>
|
||||
/// <param name="parameter">身份验证的凭证</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder AddAuthentication(string scheme, string? parameter) =>
|
||||
AddAuthentication(new AuthenticationHeaderValue(scheme, parameter));
|
||||
|
||||
/// <summary>
|
||||
/// 设置身份验证凭据请求授权标头
|
||||
/// </summary>
|
||||
@@ -1328,6 +1329,17 @@ public sealed partial class HttpRequestBuilder
|
||||
ReleaseDisposables();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置请求来源地址
|
||||
/// </summary>
|
||||
/// <remarks>设置此配置后,将在单次请求标头中添加 <c>Referer</c> 标头。</remarks>
|
||||
/// <param name="referer">请求来源地址,当设置为 <c>"{BASE_ADDRESS}"</c> 时将替换为基地址</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SetReferer(string? referer) =>
|
||||
WithHeader(HeaderNames.Referer, referer, replace: true);
|
||||
|
||||
/// <summary>
|
||||
/// 设置模拟浏览器环境
|
||||
/// </summary>
|
||||
@@ -1364,6 +1376,17 @@ public sealed partial class HttpRequestBuilder
|
||||
public HttpRequestBuilder WithAnyStatusCodeHandler(Func<HttpResponseMessage, CancellationToken, Task> handler) =>
|
||||
WithStatusCodeHandler(["*"], handler);
|
||||
|
||||
/// <summary>
|
||||
/// 添加请求成功(200-299)状态码处理程序
|
||||
/// </summary>
|
||||
/// <param name="handler">自定义处理程序</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder
|
||||
WithSuccessStatusCodeHandler(Func<HttpResponseMessage, CancellationToken, Task> handler) =>
|
||||
WithStatusCodeHandler("200-299", handler);
|
||||
|
||||
/// <summary>
|
||||
/// 添加状态码处理程序
|
||||
/// </summary>
|
||||
@@ -1590,6 +1613,107 @@ public sealed partial class HttpRequestBuilder
|
||||
? null
|
||||
: new Uri(baseAddress, UriKind.RelativeOrAbsolute));
|
||||
|
||||
/// <summary>
|
||||
/// 设置 HTTP 版本
|
||||
/// </summary>
|
||||
/// <param name="version">版本号</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SetVersion(string? version) =>
|
||||
SetVersion(string.IsNullOrWhiteSpace(version) ? null : new Version(version));
|
||||
|
||||
/// <summary>
|
||||
/// 设置 HTTP 版本
|
||||
/// </summary>
|
||||
/// <param name="version">
|
||||
/// <see cref="Version" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SetVersion(Version? version)
|
||||
{
|
||||
Version = version;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置异常抑制
|
||||
/// </summary>
|
||||
/// <remarks>抑制所有异常。重复调用仅最后一次调用生效。</remarks>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SuppressExceptions() => SuppressExceptions(true);
|
||||
|
||||
/// <summary>
|
||||
/// 设置异常抑制
|
||||
/// </summary>
|
||||
/// <remarks>重复调用仅最后一次调用生效。</remarks>
|
||||
/// <param name="enable">是否启用异常抑制。当设置为 <c>false</c> 时,将禁用异常抑制机制。</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SuppressExceptions(bool enable) => SuppressExceptions(enable ? [typeof(Exception)] : []);
|
||||
|
||||
/// <summary>
|
||||
/// 设置是否移除默认的内容的 <c>Content-Type</c>
|
||||
/// </summary>
|
||||
/// <param name="omit">如果为 <c>true</c> 则移除,默认为 <c>false</c></param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
public HttpRequestBuilder SetOmitContentType(bool omit)
|
||||
{
|
||||
OmitContentType = omit;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置异常抑制
|
||||
/// </summary>
|
||||
/// <remarks>重复调用仅最后一次调用生效。</remarks>
|
||||
/// <param name="exceptionTypes">异常抑制类型集合</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public HttpRequestBuilder SuppressExceptions(Type[] exceptionTypes)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(exceptionTypes);
|
||||
|
||||
// 检查是否包含 null 或者不是 Exception 类型的元素
|
||||
if (exceptionTypes.Any(u => (Type?)u is null || !typeof(Exception).IsAssignableFrom(u)))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"All elements in exceptionTypes must be non-null and assignable to System.Exception.");
|
||||
}
|
||||
|
||||
// 释放引用(无关紧要)
|
||||
SuppressExceptionTypes = null;
|
||||
|
||||
// 空检查
|
||||
if (exceptionTypes.Length == 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// 确保每次都能覆盖
|
||||
SuppressExceptionTypes = [];
|
||||
|
||||
// 遍历异常抑制类型集合逐条追加
|
||||
foreach (var exceptionType in exceptionTypes)
|
||||
{
|
||||
SuppressExceptionTypes.Add(exceptionType);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放可释放的对象集合
|
||||
/// </summary>
|
||||
|
@@ -157,6 +157,11 @@ public sealed partial class HttpRequestBuilder
|
||||
/// </summary>
|
||||
public Uri? BaseAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 版本
|
||||
/// </summary>
|
||||
public Version? Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HttpClient" /> 实例提供器
|
||||
/// </summary>
|
||||
@@ -181,7 +186,7 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <summary>
|
||||
/// 用于处理在设置 <see cref="HttpRequestMessage" /> 的请求消息的内容时的操作
|
||||
/// </summary>
|
||||
public Action<HttpContent?>? OnPreSetContent { get; private set; }
|
||||
public Action<HttpContent>? OnPreSetContent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于处理在发送 HTTP 请求之前的操作
|
||||
@@ -201,7 +206,13 @@ public sealed partial class HttpRequestBuilder
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="HttpMultipartFormDataBuilder" />
|
||||
/// </summary>
|
||||
internal HttpMultipartFormDataBuilder? MultipartFormDataBuilder { get; private set; }
|
||||
public HttpMultipartFormDataBuilder? MultipartFormDataBuilder { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否移除默认的内容的 <c>Content-Type</c>
|
||||
/// </summary>
|
||||
/// <remarks>默认值为:<c>false</c>。</remarks>
|
||||
public bool OmitContentType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果 HTTP 响应的 <c>IsSuccessStatusCode</c> 属性是 <c>false</c>,则引发异常。
|
||||
@@ -273,4 +284,15 @@ public sealed partial class HttpRequestBuilder
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常抑制类型集合
|
||||
/// </summary>
|
||||
/// <remarks>当配置了异常抑制类型集合后,框架将抑制(即不抛出)该集合中匹配的异常类型。</remarks>
|
||||
internal HashSet<Type>? SuppressExceptionTypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 超时发生时要执行的操作
|
||||
/// </summary>
|
||||
internal Action? TimeoutAction { get; private set; }
|
||||
}
|
@@ -10,6 +10,9 @@
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
@@ -614,4 +617,116 @@ public sealed partial class HttpRequestBuilder
|
||||
/// </returns>
|
||||
public static HttpDeclarativeBuilder Declarative(MethodInfo method, object?[] args) =>
|
||||
new(method, args);
|
||||
|
||||
/// <summary>
|
||||
/// 从 JSON 中创建 <see cref="HttpRequestBuilder" /> 实例
|
||||
/// </summary>
|
||||
/// <param name="json">JSON 字符串</param>
|
||||
/// <param name="configure">自定义配置委托</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static HttpRequestBuilder FromJson(string json, Action<HttpRequestBuilder>? configure = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(json);
|
||||
|
||||
/*
|
||||
* 手动解析 JSON 字符串
|
||||
*
|
||||
* 不采用 JSON 反序列化的原因如下:
|
||||
* 1. HttpRequestBuilder 的属性设计为只读,无法直接通过反序列化赋值。
|
||||
* 2. 避免引入 [JsonInclude] 特性对 System.Text.Json 的强耦合,保持依赖解耦。
|
||||
* 3. 简化 JSON 字符串的结构定义,无需严格遵循 HttpRequestBuilder 的属性定义,从而省略 [JsonPropertyName] 等自定义映射。
|
||||
* 4. 精确控制需要解析的键,减少不必要的自定义 JsonConverter 操作,提升性能与可维护性。
|
||||
*/
|
||||
var jsonObject = JsonNode.Parse(json, new JsonNodeOptions { PropertyNameCaseInsensitive = true },
|
||||
new JsonDocumentOptions { AllowTrailingCommas = true })?.AsObject();
|
||||
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(jsonObject);
|
||||
|
||||
// 验证必填字段
|
||||
if (!jsonObject.TryGetPropertyValue("method", out var methodNode) || methodNode is not JsonValue methodValue)
|
||||
{
|
||||
throw new ArgumentException("Missing required `method` in JSON.");
|
||||
}
|
||||
|
||||
// 允许 "url" 为 null,但必须定义
|
||||
if (!jsonObject.ContainsKey("url"))
|
||||
{
|
||||
throw new ArgumentException("Missing required `url` in JSON.");
|
||||
}
|
||||
|
||||
// 初始化 HttpRequestBuilder 实例
|
||||
var httpRequestBuilder = Create(methodValue.ToString(), jsonObject["url"]?.GetValue<string?>());
|
||||
|
||||
// 处理可选字段
|
||||
HandleJsonNode(jsonObject, "baseAddress", node => httpRequestBuilder.SetBaseAddress(node.GetValue<string>()));
|
||||
HandleJsonNode(jsonObject, "headers", node => httpRequestBuilder.WithHeaders(node));
|
||||
HandleJsonNode(jsonObject, "queries", node => httpRequestBuilder.WithQueryParameters(node));
|
||||
HandleJsonNode(jsonObject, "cookies", node => httpRequestBuilder.WithCookies(node));
|
||||
HandleJsonNode(jsonObject, "timeout", node => httpRequestBuilder.SetTimeout(node.GetValue<double>()));
|
||||
HandleJsonNode(jsonObject, "client", node => httpRequestBuilder.SetHttpClientName(node.GetValue<string?>()));
|
||||
HandleJsonNode(jsonObject, "profiler", node => httpRequestBuilder.Profiler(node.GetValue<bool>()));
|
||||
|
||||
// 处理请求内容
|
||||
if (jsonObject.TryGetPropertyValue("data", out var dataNode))
|
||||
{
|
||||
// "data" 和 "contentType" 必须同时存在或同时不存在
|
||||
if (!jsonObject.TryGetPropertyValue("contentType", out var contentTypeNode) ||
|
||||
contentTypeNode is not JsonValue contentTypeValue)
|
||||
{
|
||||
throw new InvalidOperationException("The `contentType` key is required when `data` is present.");
|
||||
}
|
||||
|
||||
// 设置请求内容
|
||||
httpRequestBuilder
|
||||
.SetContent(
|
||||
dataNode?.ToJsonString(new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
}), contentTypeValue.ToString()).AddStringContentForFormUrlEncodedContentProcessor();
|
||||
|
||||
// 设置内容编码
|
||||
HandleJsonNode(jsonObject, "encoding",
|
||||
node => httpRequestBuilder.SetContentEncoding(node.GetValue<string>()));
|
||||
}
|
||||
|
||||
// 处理多部分表单
|
||||
if (jsonObject.TryGetPropertyValue("multipart", out var multipartNode))
|
||||
{
|
||||
// 设置多部分表单内容
|
||||
httpRequestBuilder.SetMultipartContent(multipart => multipart.AddJson(multipartNode?.AsObject()
|
||||
.ToJsonString(new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping })));
|
||||
}
|
||||
|
||||
// 调用自定义配置委托
|
||||
configure?.Invoke(httpRequestBuilder);
|
||||
|
||||
return httpRequestBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 <see cref="JsonNode" />
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">
|
||||
/// <see cref="JsonObject" />
|
||||
/// </param>
|
||||
/// <param name="propertyName">属性名</param>
|
||||
/// <param name="action">自定义操作</param>
|
||||
internal static void HandleJsonNode(JsonObject jsonObject, string propertyName, Action<JsonNode> action)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(jsonObject);
|
||||
ArgumentNullException.ThrowIfNull(propertyName);
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
if (jsonObject.TryGetPropertyValue(propertyName, out var node) && node is not null)
|
||||
{
|
||||
action(node);
|
||||
}
|
||||
}
|
||||
}
|
@@ -76,6 +76,12 @@ public sealed partial class HttpRequestBuilder
|
||||
// 初始化 HttpRequestMessage 实例
|
||||
var httpRequestMessage = new HttpRequestMessage(HttpMethod, finalRequestUri);
|
||||
|
||||
// 设置 HTTP 版本
|
||||
if (Version is not null)
|
||||
{
|
||||
httpRequestMessage.Version = Version;
|
||||
}
|
||||
|
||||
// 启用性能优化
|
||||
EnablePerformanceOptimization(httpRequestMessage);
|
||||
|
||||
@@ -160,18 +166,44 @@ public sealed partial class HttpRequestBuilder
|
||||
/// </param>
|
||||
internal void AppendPathSegments(UriBuilder uriBuilder)
|
||||
{
|
||||
// 空检查
|
||||
if ((PathSegments == null || PathSegments.Count == 0) &&
|
||||
(PathSegmentsToRemove == null || PathSegmentsToRemove.Count == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录原路径是否以斜杠结尾(修复核心逻辑)
|
||||
var originalPath = uriBuilder.Uri.AbsolutePath;
|
||||
var endsWithSlash = originalPath.Length > 1 && originalPath.EndsWith('/');
|
||||
|
||||
// 解析 URL 中的路径片段列表
|
||||
var pathSegments = uriBuilder.Path.Split('/', StringSplitOptions.RemoveEmptyEntries).Concat([]);
|
||||
var pathSegments = uriBuilder.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 追加路径片段
|
||||
pathSegments = pathSegments.Concat(PathSegments.ConcatIgnoreNull([]).Where(u => !string.IsNullOrWhiteSpace(u))
|
||||
.Select(u => u.TrimStart('/').TrimEnd('/')));
|
||||
// 追加并处理新路径片段
|
||||
var newPathSegments = pathSegments.Concat(PathSegments.ConcatIgnoreNull([])
|
||||
.Where(u => !string.IsNullOrWhiteSpace(u)).Select(u => u.TrimStart('/').TrimEnd('/')));
|
||||
|
||||
// 构建路径片段赋值给 UriBuilder 的 Path 属性
|
||||
uriBuilder.Path = '/' + string.Join('/',
|
||||
// 过滤已标记为移除的路径片段
|
||||
pathSegments.WhereIf(PathSegmentsToRemove is { Count: > 0 },
|
||||
u => PathSegmentsToRemove?.TryGetValue(u, out _) == false));
|
||||
// 过滤需要移除的路径片段
|
||||
var filteredSegments = newPathSegments.WhereIf(PathSegmentsToRemove is { Count: > 0 },
|
||||
u => PathSegmentsToRemove?.Contains(u) == false).ToArray();
|
||||
|
||||
// 构建最终路径
|
||||
if (filteredSegments.Length != 0)
|
||||
{
|
||||
uriBuilder.Path = $"/{string.Join('/', filteredSegments)}";
|
||||
|
||||
// 恢复原路径的结尾斜杠(当存在路径片段时)
|
||||
if (endsWithSlash)
|
||||
{
|
||||
uriBuilder.Path += "/";
|
||||
}
|
||||
}
|
||||
// 没有路径片段时设置为根路径
|
||||
else
|
||||
{
|
||||
uriBuilder.Path = "/";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,6 +214,13 @@ public sealed partial class HttpRequestBuilder
|
||||
/// </param>
|
||||
internal void AppendQueryParameters(UriBuilder uriBuilder)
|
||||
{
|
||||
// 空检查
|
||||
if ((QueryParameters is null || QueryParameters.Count == 0) &&
|
||||
(QueryParametersToRemove is null || QueryParametersToRemove.Count == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析 URL 中的查询字符串为键值对列表
|
||||
var queryParameters = uriBuilder.Query.ParseFormatKeyValueString(['&'], '?');
|
||||
|
||||
@@ -300,6 +339,16 @@ public sealed partial class HttpRequestBuilder
|
||||
// 遍历请求标头集合并追加到 HttpRequestMessage.Headers 中
|
||||
foreach (var (key, values) in Headers)
|
||||
{
|
||||
// 替换 Referer 标头的 "{BASE_ADDRESS}" 模板字符串
|
||||
if (key.IsIn([HeaderNames.Referer], StringComparer.OrdinalIgnoreCase) &&
|
||||
values.FirstOrDefault() == Constants.REFERER_HEADER_BASE_ADDRESS_TEMPLATE)
|
||||
{
|
||||
httpRequestMessage.Headers.Referrer = new Uri(
|
||||
$"{httpRequestMessage.RequestUri?.Scheme}://{httpRequestMessage.RequestUri?.Host}{(httpRequestMessage.RequestUri?.IsDefaultPort != true ? $":{httpRequestMessage.RequestUri?.Port}" : string.Empty)}",
|
||||
UriKind.RelativeOrAbsolute);
|
||||
continue;
|
||||
}
|
||||
|
||||
httpRequestMessage.Headers.TryAddWithoutValidation(key, values);
|
||||
}
|
||||
}
|
||||
@@ -486,6 +535,18 @@ public sealed partial class HttpRequestBuilder
|
||||
// 构建 HttpContent 实例
|
||||
var httpContent = httpContentProcessorFactory.Build(RawContent, ContentType!, ContentEncoding, processors);
|
||||
|
||||
// 空检查
|
||||
if (httpContent is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否移除默认的内容的 Content-Type,解决对接 Java 程序时可能出现失败问题
|
||||
if (OmitContentType)
|
||||
{
|
||||
httpContent.Headers.ContentType = null;
|
||||
}
|
||||
|
||||
// 调用用于处理在设置请求消息的内容时的操作
|
||||
OnPreSetContent?.Invoke(httpContent);
|
||||
|
||||
@@ -513,6 +574,9 @@ public sealed partial class HttpRequestBuilder
|
||||
{
|
||||
httpRequestMessage.Options.AddOrUpdate(Constants.DISABLED_PROFILER_KEY, "TRUE");
|
||||
}
|
||||
|
||||
// 添加 HttpClient 实例的配置名称
|
||||
httpRequestMessage.Options.AddOrUpdate(Constants.HTTP_CLIENT_NAME, HttpClientName ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -88,15 +88,26 @@ internal static class Constants
|
||||
/// <remarks>被用于从 <see cref="HttpRequestMessage" /> 的 <c>Options</c> 属性中读取。</remarks>
|
||||
internal const string DECLARATIVE_METHOD_KEY = "__DECLARATIVE_METHOD__";
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 请求 <see cref="HttpClient" /> 实例的配置名称键
|
||||
/// </summary>
|
||||
/// <remarks>被用于从 <see cref="HttpRequestMessage" /> 的 <c>Options</c> 属性中读取。</remarks>
|
||||
internal const string HTTP_CLIENT_NAME = "__HTTP_CLIENT_NAME__";
|
||||
|
||||
/// <summary>
|
||||
/// 浏览器的 <c>User-Agent</c> 标头值
|
||||
/// </summary>
|
||||
internal const string USER_AGENT_OF_BROWSER =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0";
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// 移动端浏览器的 <c>User-Agent</c> 标头值
|
||||
/// </summary>
|
||||
internal const string USER_AGENT_OF_MOBILE_BROWSER =
|
||||
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Mobile Safari/537.36 Edg/133.0.0.0";
|
||||
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36 Edg/135.0.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// <c>Referer</c> 标头请求基地址模板
|
||||
/// </summary>
|
||||
internal const string REFERER_HEADER_BASE_ADDRESS_TEMPLATE = "{BASE_ADDRESS}";
|
||||
}
|
@@ -13,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
@@ -27,16 +28,45 @@ public class ObjectContentConverter : IHttpContentConverter
|
||||
/// <inheritdoc />
|
||||
public virtual object? Read(Type resultType, HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
httpResponseMessage.Content.ReadFromJsonAsync(resultType,
|
||||
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
|
||||
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).GetAwaiter().GetResult();
|
||||
httpResponseMessage.Content
|
||||
.ReadFromJsonAsync(resultType, GetJsonSerializerOptions(httpResponseMessage), cancellationToken)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await httpResponseMessage.Content.ReadFromJsonAsync(resultType,
|
||||
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
|
||||
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).ConfigureAwait(false);
|
||||
await httpResponseMessage.Content.ReadFromJsonAsync(resultType, GetJsonSerializerOptions(httpResponseMessage),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// 获取 JSON 序列化选项实例
|
||||
/// </summary>
|
||||
/// <param name="httpResponseMessage">
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="JsonSerializerOptions" />
|
||||
/// </returns>
|
||||
protected virtual JsonSerializerOptions GetJsonSerializerOptions(HttpResponseMessage httpResponseMessage)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(httpResponseMessage);
|
||||
|
||||
// 获取 HttpClient 实例的配置名称
|
||||
if (httpResponseMessage.RequestMessage?.Options.TryGetValue(
|
||||
new HttpRequestOptionsKey<string>(Constants.HTTP_CLIENT_NAME), out var httpClientName) != true)
|
||||
{
|
||||
httpClientName = string.Empty;
|
||||
}
|
||||
|
||||
// 获取 HttpClientOptions 实例
|
||||
var httpClientOptions = ServiceProvider?.GetService<IOptionsMonitor<HttpClientOptions>>()?.Get(httpClientName);
|
||||
|
||||
// 优先级:指定名称的 HttpClientOptions -> HttpRemoteOptions -> 默认值
|
||||
return (httpClientOptions?.IsDefault != false ? null : httpClientOptions.JsonSerializerOptions) ??
|
||||
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
|
||||
HttpRemoteOptions.JsonSerializerOptionsDefault;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,14 +78,13 @@ public class ObjectContentConverter<TResult> : ObjectContentConverter, IHttpCont
|
||||
/// <inheritdoc />
|
||||
public virtual TResult? Read(HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
httpResponseMessage.Content.ReadFromJsonAsync<TResult>(
|
||||
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
|
||||
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).GetAwaiter().GetResult();
|
||||
httpResponseMessage.Content
|
||||
.ReadFromJsonAsync<TResult>(GetJsonSerializerOptions(httpResponseMessage), cancellationToken).GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<TResult?> ReadAsync(HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await httpResponseMessage.Content.ReadFromJsonAsync<TResult>(
|
||||
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
|
||||
HttpRemoteOptions.JsonSerializerOptionsDefault, cancellationToken).ConfigureAwait(false);
|
||||
await httpResponseMessage.Content.ReadFromJsonAsync<TResult>(GetJsonSerializerOptions(httpResponseMessage),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
@@ -18,10 +18,10 @@ public class VoidContentConverter : HttpContentConverterBase<VoidContent>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override VoidContent? Read(HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default) => default;
|
||||
CancellationToken cancellationToken = default) => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<VoidContent?> ReadAsync(HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult<VoidContent?>(default);
|
||||
Task.FromResult<VoidContent?>(null);
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 声明式 HTTP 版本特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]
|
||||
public sealed class HttpVersionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="HttpVersionAttribute" />
|
||||
/// </summary>
|
||||
/// <param name="version">HTTP 版本</param>
|
||||
public HttpVersionAttribute(string? version) => Version = version;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 版本
|
||||
/// </summary>
|
||||
public string? Version { get; set; }
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 声明式请求来源地址特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]
|
||||
public sealed class RefererAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="RefererAttribute" />
|
||||
/// </summary>
|
||||
/// <param name="referer">请求来源地址,当设置为 <c>"{BASE_ADDRESS}"</c> 时将替换为基地址</param>
|
||||
public RefererAttribute(string? referer) => Referer = referer;
|
||||
|
||||
/// <summary>
|
||||
/// 请求来源地址,当设置为 <c>"{BASE_ADDRESS}"</c> 时将替换为基地址
|
||||
/// </summary>
|
||||
public string? Referer { get; set; }
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 声明式异常抑制特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface)]
|
||||
public sealed class SuppressExceptionsAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="SuppressExceptionsAttribute" />
|
||||
/// </summary>
|
||||
/// <remarks>抑制所有异常。</remarks>
|
||||
public SuppressExceptionsAttribute()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="SuppressExceptionsAttribute" />
|
||||
/// </summary>
|
||||
/// <param name="enabled">是否启用异常抑制。当设置为 <c>false</c> 时,将禁用异常抑制机制。</param>
|
||||
public SuppressExceptionsAttribute(bool enabled) => Types = enabled ? [typeof(Exception)] : [];
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="SuppressExceptionsAttribute" />
|
||||
/// </summary>
|
||||
/// <param name="types">异常抑制类型集合</param>
|
||||
public SuppressExceptionsAttribute(params Type[] types) => Types = types;
|
||||
|
||||
/// <summary>
|
||||
/// 异常抑制类型集合
|
||||
/// </summary>
|
||||
public Type[] Types { get; set; }
|
||||
}
|
@@ -44,8 +44,11 @@ public sealed class HttpDeclarativeBuilder
|
||||
new(typeof(QueryDeclarativeExtractor), new QueryDeclarativeExtractor()),
|
||||
new(typeof(PathDeclarativeExtractor), new PathDeclarativeExtractor()),
|
||||
new(typeof(CookieDeclarativeExtractor), new CookieDeclarativeExtractor()),
|
||||
new(typeof(RefererDeclarativeExtractor), new RefererDeclarativeExtractor()),
|
||||
new(typeof(HeaderDeclarativeExtractor), new HeaderDeclarativeExtractor()),
|
||||
new(typeof(PropertyDeclarativeExtractor), new PropertyDeclarativeExtractor()),
|
||||
new(typeof(HttpVersionDeclarativeExtractor), new HttpVersionDeclarativeExtractor()),
|
||||
new(typeof(SuppressExceptionsDeclarativeExtractor), new SuppressExceptionsDeclarativeExtractor()),
|
||||
new(typeof(BodyDeclarativeExtractor), new BodyDeclarativeExtractor())
|
||||
]);
|
||||
|
||||
|
@@ -45,7 +45,7 @@ internal sealed class HeaderDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
if (headerAttribute.HasSetValue)
|
||||
{
|
||||
httpRequestBuilder.WithHeader(headerName, headerAttribute.Value, headerAttribute.Escape,
|
||||
replace: headerAttribute.Replace);
|
||||
headerAttribute.Replace);
|
||||
}
|
||||
// 移除请求标头
|
||||
else
|
||||
@@ -91,7 +91,7 @@ internal sealed class HeaderDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
if (parameter.ParameterType.IsBaseTypeOrEnumOrCollection())
|
||||
{
|
||||
httpRequestBuilder.WithHeader(parameterName, value ?? headerAttribute.Value,
|
||||
headerAttribute.Escape, replace: headerAttribute.Replace);
|
||||
headerAttribute.Escape, headerAttribute.Replace);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -99,7 +99,7 @@ internal sealed class HeaderDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
// 空检查
|
||||
if (value is not null)
|
||||
{
|
||||
httpRequestBuilder.WithHeaders(value, headerAttribute.Escape, replace: headerAttribute.Replace);
|
||||
httpRequestBuilder.WithHeaders(value, headerAttribute.Escape, headerAttribute.Replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,31 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 声明式 <see cref="HttpVersionAttribute" /> 特性提取器
|
||||
/// </summary>
|
||||
internal sealed class HttpVersionDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
|
||||
{
|
||||
// 检查方法或接口是否贴有 [HttpVersion] 特性
|
||||
if (!context.IsMethodDefined<HttpVersionAttribute>(out var versionAttribute, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置 HTTP 版本
|
||||
httpRequestBuilder.SetVersion(versionAttribute.Version);
|
||||
}
|
||||
}
|
@@ -45,7 +45,7 @@ internal sealed class QueryDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
if (queryAttribute.HasSetValue)
|
||||
{
|
||||
httpRequestBuilder.WithQueryParameter(queryName, queryAttribute.Value, queryAttribute.Escape,
|
||||
replace: queryAttribute.Replace, ignoreNullValues: queryAttribute.IgnoreNullValues);
|
||||
queryAttribute.Replace, queryAttribute.IgnoreNullValues);
|
||||
}
|
||||
// 移除查询参数
|
||||
else
|
||||
@@ -91,8 +91,7 @@ internal sealed class QueryDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
if (parameter.ParameterType.IsBaseTypeOrEnumOrCollection())
|
||||
{
|
||||
httpRequestBuilder.WithQueryParameter(parameterName, value ?? queryAttribute.Value,
|
||||
queryAttribute.Escape, replace: queryAttribute.Replace,
|
||||
ignoreNullValues: queryAttribute.IgnoreNullValues);
|
||||
queryAttribute.Escape, queryAttribute.Replace, queryAttribute.IgnoreNullValues);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -101,7 +100,7 @@ internal sealed class QueryDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
if (value is not null)
|
||||
{
|
||||
httpRequestBuilder.WithQueryParameters(value, queryAttribute.Prefix, queryAttribute.Escape,
|
||||
replace: queryAttribute.Replace, ignoreNullValues: queryAttribute.IgnoreNullValues);
|
||||
queryAttribute.Replace, queryAttribute.IgnoreNullValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,31 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 声明式 <see cref="RefererAttribute" /> 特性提取器
|
||||
/// </summary>
|
||||
internal sealed class RefererDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
|
||||
{
|
||||
// 检查方法或接口是否贴有 [Referer] 特性
|
||||
if (!context.IsMethodDefined<RefererAttribute>(out var refererAttribute, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置请求来源地址
|
||||
httpRequestBuilder.SetReferer(refererAttribute.Referer);
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 声明式 <see cref="SuppressExceptionsAttribute" /> 特性提取器
|
||||
/// </summary>
|
||||
internal sealed class SuppressExceptionsDeclarativeExtractor : IHttpDeclarativeExtractor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
|
||||
{
|
||||
// 检查方法或接口是否贴有 [SuppressExceptions] 特性
|
||||
if (!context.IsMethodDefined<SuppressExceptionsAttribute>(out var suppressExceptionsAttribute, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置异常抑制
|
||||
httpRequestBuilder.SuppressExceptions(suppressExceptionsAttribute.Types);
|
||||
}
|
||||
}
|
@@ -25,7 +25,7 @@ public sealed class HttpDeclarativeExtractorContext
|
||||
/// 冻结参数类型集合
|
||||
/// </summary>
|
||||
/// <remarks>此类参数类型不应作为外部提取对象。</remarks>
|
||||
internal static Type[] _frozenParameterTypes =
|
||||
internal static readonly Type[] _frozenParameterTypes =
|
||||
[
|
||||
typeof(Action<HttpRequestBuilder>), typeof(Action<HttpMultipartFormDataBuilder>), typeof(HttpCompletionOption),
|
||||
typeof(CancellationToken)
|
||||
|
@@ -42,7 +42,7 @@ public static partial class HttpContextExtensions
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
internal static HashSet<string> _ignoreResponseHeaders =
|
||||
internal static readonly HashSet<string> _ignoreResponseHeaders =
|
||||
[
|
||||
"Content-Type", "Connection", "Transfer-Encoding", "Keep-Alive", "Upgrade", "Proxy-Connection"
|
||||
];
|
||||
@@ -64,7 +64,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static HttpResponseMessage Forward(this HttpContext? httpContext, string? requestUri = null,
|
||||
public static HttpResponseMessage? Forward(this HttpContext? httpContext, string? requestUri = null,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -90,7 +90,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static HttpResponseMessage Forward(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
public static HttpResponseMessage? Forward(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -115,7 +115,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static HttpResponseMessage Forward(this HttpContext? httpContext, Uri? requestUri = null,
|
||||
public static HttpResponseMessage? Forward(this HttpContext? httpContext, Uri? requestUri = null,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -140,7 +140,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static HttpResponseMessage Forward(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
public static HttpResponseMessage? Forward(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null)
|
||||
@@ -180,7 +180,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, string? requestUri = null,
|
||||
public static Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, string? requestUri = null,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -206,7 +206,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
public static Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -231,7 +231,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, Uri? requestUri = null,
|
||||
public static Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, Uri? requestUri = null,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -256,7 +256,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
public static async Task<HttpResponseMessage> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
public static async Task<HttpResponseMessage?> ForwardAsync(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null)
|
||||
@@ -297,7 +297,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, string? requestUri = null,
|
||||
public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, string? requestUri = null,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -324,7 +324,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -350,7 +350,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, Uri? requestUri = null,
|
||||
public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, Uri? requestUri = null,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -376,7 +376,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static HttpRemoteResult<TResult> Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
public static HttpRemoteResult<TResult>? Forward<TResult>(this HttpContext? httpContext, HttpMethod httpMethod,
|
||||
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null)
|
||||
@@ -393,7 +393,7 @@ public static partial class HttpContextExtensions
|
||||
var result = httpRemoteService.Send<TResult>(httpRequestBuilder, completionOption, httpContext.RequestAborted);
|
||||
|
||||
// 根据配置选项将 HttpResponseMessage 信息转发到 HttpContext 中
|
||||
ForwardResponseMessage(httpContext, result.ResponseMessage, httpContextForwardBuilder.ForwardOptions);
|
||||
ForwardResponseMessage(httpContext, result?.ResponseMessage, httpContextForwardBuilder.ForwardOptions);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -416,7 +416,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
public static Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -443,7 +443,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
public static Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
HttpMethod httpMethod, string? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -469,7 +469,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
public static Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null) =>
|
||||
@@ -495,7 +495,7 @@ public static partial class HttpContextExtensions
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
public static async Task<HttpRemoteResult<TResult>> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
public static async Task<HttpRemoteResult<TResult>?> ForwardAsync<TResult>(this HttpContext? httpContext,
|
||||
HttpMethod httpMethod, Uri? requestUri = null, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
|
||||
HttpContextForwardOptions? forwardOptions = null)
|
||||
@@ -513,7 +513,7 @@ public static partial class HttpContextExtensions
|
||||
httpContext.RequestAborted).ConfigureAwait(false);
|
||||
|
||||
// 根据配置选项将 HttpResponseMessage 信息转发到 HttpContext 中
|
||||
ForwardResponseMessage(httpContext, result.ResponseMessage, httpContextForwardBuilder.ForwardOptions);
|
||||
ForwardResponseMessage(httpContext, result?.ResponseMessage, httpContextForwardBuilder.ForwardOptions);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -605,14 +605,22 @@ public static partial class HttpContextExtensions
|
||||
/// <param name="forwardOptions">
|
||||
/// <see cref="HttpContextForwardOptions" />
|
||||
/// </param>
|
||||
internal static void ForwardResponseMessage(HttpContext httpContext, HttpResponseMessage httpResponseMessage,
|
||||
internal static void ForwardResponseMessage(HttpContext httpContext, HttpResponseMessage? httpResponseMessage,
|
||||
HttpContextForwardOptions forwardOptions)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(httpContext);
|
||||
ArgumentNullException.ThrowIfNull(httpResponseMessage);
|
||||
ArgumentNullException.ThrowIfNull(forwardOptions);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 HttpResponse 实例
|
||||
var httpResponse = httpContext.Response;
|
||||
|
||||
|
@@ -0,0 +1,88 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HttpMultipartFormDataBuilder" /> 拓展类
|
||||
/// </summary>
|
||||
public static class HttpMultipartFormDataBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加文件
|
||||
/// </summary>
|
||||
/// <param name="httpMultipartFormDataBuilder">
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </param>
|
||||
/// <param name="formFile">
|
||||
/// <see cref="IFormFile" />
|
||||
/// </param>
|
||||
/// <param name="name">表单名称</param>
|
||||
/// <param name="fileName">文件的名称</param>
|
||||
/// <param name="contentType">内容类型</param>
|
||||
/// <param name="contentEncoding">内容编码</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </returns>
|
||||
public static HttpMultipartFormDataBuilder AddFile(this HttpMultipartFormDataBuilder httpMultipartFormDataBuilder,
|
||||
IFormFile formFile, string? name = null, string? fileName = null, string? contentType = null,
|
||||
Encoding? contentEncoding = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(formFile);
|
||||
|
||||
// 初始化 MemoryStream 实例
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
// 将 IFormFile 内容复制到内存流
|
||||
formFile.CopyTo(memoryStream);
|
||||
|
||||
// 将内存流的位置重置到起始位置
|
||||
memoryStream.Position = 0;
|
||||
|
||||
// 添加文件流
|
||||
return httpMultipartFormDataBuilder.AddStream(memoryStream, name ?? formFile.Name,
|
||||
fileName ?? formFile.FileName, contentType ?? formFile.ContentType, contentEncoding,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加多个文件
|
||||
/// </summary>
|
||||
/// <param name="httpMultipartFormDataBuilder">
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </param>
|
||||
/// <param name="formFiles">
|
||||
/// <see cref="IFormFileCollection" />
|
||||
/// </param>
|
||||
/// <param name="name">表单名称</param>
|
||||
/// <returns>
|
||||
/// <see cref="HttpMultipartFormDataBuilder" />
|
||||
/// </returns>
|
||||
public static HttpMultipartFormDataBuilder AddFiles(this HttpMultipartFormDataBuilder httpMultipartFormDataBuilder,
|
||||
IEnumerable<IFormFile> formFiles, string? name = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(formFiles);
|
||||
|
||||
// 逐条添加文件
|
||||
foreach (var formFile in formFiles)
|
||||
{
|
||||
httpMultipartFormDataBuilder.AddFile(formFile, name ?? formFile.Name);
|
||||
}
|
||||
|
||||
return httpMultipartFormDataBuilder;
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ using Microsoft.Net.Http.Headers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
using ThingsGateway.Utilities;
|
||||
@@ -27,7 +28,7 @@ namespace ThingsGateway.HttpRemote.Extensions;
|
||||
/// <summary>
|
||||
/// HTTP 远程服务拓展类
|
||||
/// </summary>
|
||||
public static class HttpRemoteExtensions
|
||||
public static partial class HttpRemoteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加 HTTP 远程请求分析工具处理委托
|
||||
@@ -70,6 +71,56 @@ public static class HttpRemoteExtensions
|
||||
builder.AddProfilerDelegatingHandler(() =>
|
||||
disableInProduction && GetHostEnvironmentName(builder.Services)?.ToLower() == "production");
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="HttpClient" /> 额外选项
|
||||
/// </summary>
|
||||
/// <param name="builder">
|
||||
/// <see cref="IHttpClientBuilder" />
|
||||
/// </param>
|
||||
/// <param name="configure">自定义配置选项</param>
|
||||
/// <returns>
|
||||
/// <see cref="IHttpClientBuilder" />
|
||||
/// </returns>
|
||||
public static IHttpClientBuilder ConfigureOptions(this IHttpClientBuilder builder,
|
||||
Action<HttpClientOptions> configure)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
|
||||
builder.Services.AddOptions<HttpClientOptions>(builder.Name).Configure(options =>
|
||||
{
|
||||
options.IsDefault = false;
|
||||
configure.Invoke(options);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置 <see cref="HttpClient" /> 额外选项
|
||||
/// </summary>
|
||||
/// <param name="builder">
|
||||
/// <see cref="IHttpClientBuilder" />
|
||||
/// </param>
|
||||
/// <param name="configure">自定义配置选项</param>
|
||||
/// <returns>
|
||||
/// <see cref="IHttpClientBuilder" />
|
||||
/// </returns>
|
||||
public static IHttpClientBuilder ConfigureOptions(this IHttpClientBuilder builder,
|
||||
Action<HttpClientOptions, IServiceProvider> configure)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
|
||||
builder.Services.AddOptions<HttpClientOptions>(builder.Name).Configure<IServiceProvider>((options, provider) =>
|
||||
{
|
||||
options.IsDefault = false;
|
||||
configure.Invoke(options, provider);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 <see cref="HttpClient" /> 启用性能优化
|
||||
/// </summary>
|
||||
@@ -160,17 +211,26 @@ public static class HttpRemoteExtensions
|
||||
? [new KeyValuePair<string, IEnumerable<string>>("Declarative", [methodSignature])]
|
||||
: null;
|
||||
|
||||
// 格式化 HttpClient 实例的配置条目
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? httpClientKeyValues =
|
||||
httpRequestMessage.Options.TryGetValue(new HttpRequestOptionsKey<string>(Constants.HTTP_CLIENT_NAME),
|
||||
out var httpClientName)
|
||||
? [new KeyValuePair<string, IEnumerable<string>>("HttpClient Name", [httpClientName])]
|
||||
: null;
|
||||
|
||||
// 格式化常规条目
|
||||
var generalEntry = StringUtility.FormatKeyValuesSummary(new[]
|
||||
{
|
||||
new KeyValuePair<string, IEnumerable<string>>("Request URL",
|
||||
[httpRequestMessage.RequestUri?.OriginalString!]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("HTTP Method", [httpRequestMessage.Method.ToString()]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("Status Code",
|
||||
[$"{(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("HTTP Content",
|
||||
[$"{httpContent?.GetType().Name}"])
|
||||
}.ConcatIgnoreNull(declarativeKeyValues).ConcatIgnoreNull(generalCustomKeyValues), generalSummary);
|
||||
{
|
||||
new KeyValuePair<string, IEnumerable<string>>("Request URL",
|
||||
[httpRequestMessage.RequestUri?.OriginalString!]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("HTTP Method", [httpRequestMessage.Method.ToString()]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("Status Code",
|
||||
[$"{(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("HTTP Version", [httpResponseMessage.Version.ToString()]),
|
||||
new KeyValuePair<string, IEnumerable<string>>("HTTP Content",
|
||||
[$"{httpContent?.GetType().Name}"])
|
||||
}.ConcatIgnoreNull(httpClientKeyValues).ConcatIgnoreNull(declarativeKeyValues)
|
||||
.ConcatIgnoreNull(generalCustomKeyValues), generalSummary);
|
||||
|
||||
// 格式化响应条目
|
||||
var responseEntry = httpResponseMessage.ProfilerHeaders(responseSummary);
|
||||
@@ -203,19 +263,43 @@ public static class HttpRemoteExtensions
|
||||
// 默认只读取 5KB 的内容
|
||||
const int maxBytesToDisplay = 5120;
|
||||
|
||||
// 读取内容为字节数组
|
||||
/*
|
||||
* 读取内容为字节数组
|
||||
*
|
||||
* 由于 HttpContent 的流设计为单次读取(即流内容在首次读取后会被消耗,无法重复读取),
|
||||
* 当前实现(即使用 ReadAsByteArrayAsync(cancellationToken))中对于较大内容会一次性加载至内存,
|
||||
* 这可能导致性能问题(如内存占用过高或响应延迟),不过目前尚未找到更优的解决方案。
|
||||
*
|
||||
* 强烈建议在生产环境中禁用或关闭此类一次性读取操作,尤其是对于高并发或大流量场景,
|
||||
* 以避免因内存溢出(OOM)或线程阻塞导致的服务不可用风险。
|
||||
*/
|
||||
var buffer = await httpContent.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
var total = buffer.Length;
|
||||
|
||||
// 计算要显示的部分
|
||||
var bytesToShow = Math.Min(buffer.Length, maxBytesToDisplay);
|
||||
var partialContent = Encoding.UTF8.GetString(buffer, 0, bytesToShow);
|
||||
var bytesToShow = Math.Min(total, maxBytesToDisplay);
|
||||
|
||||
// 注册 CodePagesEncodingProvider,使得程序能够识别并使用 Windows 代码页中的各种编码
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
// 获取内容编码
|
||||
var charset = httpContent.Headers.ContentType?.CharSet ?? "utf-8";
|
||||
var partialContent = Encoding.GetEncoding(charset).GetString(buffer, 0, bytesToShow);
|
||||
|
||||
// 检查是否是完整的 Unicode 转义字符串
|
||||
if (total == bytesToShow && UnicodeEscapeRegex().IsMatch(partialContent))
|
||||
{
|
||||
partialContent = Regex.Unescape(partialContent);
|
||||
}
|
||||
|
||||
// 如果实际读取的数据小于最大显示大小,则直接返回;否则,添加省略号表示内容被截断
|
||||
var bodyString = buffer.Length <= maxBytesToDisplay ? partialContent : partialContent + " ... [truncated]";
|
||||
var bodyString = total <= maxBytesToDisplay
|
||||
? partialContent
|
||||
: partialContent + $" ... [truncated, total: {total} bytes]";
|
||||
|
||||
return StringUtility.FormatKeyValuesSummary(
|
||||
[new KeyValuePair<string, IEnumerable<string>>(string.Empty, [bodyString])],
|
||||
$"{summary} ({httpContent.GetType().Name})");
|
||||
$"{summary} ({httpContent.GetType().Name}, total: {total} bytes)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -359,4 +443,13 @@ public static class HttpRemoteExtensions
|
||||
? null
|
||||
: Convert.ToString(hostEnvironment.GetType().GetProperty("EnvironmentName")?.GetValue(hostEnvironment));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unicode 转义正则表达式
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see cref="Regex" />
|
||||
/// </returns>
|
||||
[GeneratedRegex(@"\\u([0-9a-fA-F]{4})")]
|
||||
private static partial Regex UnicodeEscapeRegex();
|
||||
}
|
@@ -52,27 +52,44 @@ internal sealed class HttpContentConverterFactory : IHttpContentConverterFactory
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TResult? Read<TResult>(HttpResponseMessage httpResponseMessage, IHttpContentConverter[]? converters = null,
|
||||
public TResult? Read<TResult>(HttpResponseMessage? httpResponseMessage, IHttpContentConverter[]? converters = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
GetConverter<TResult>(converters).Read(httpResponseMessage, cancellationToken);
|
||||
httpResponseMessage is null
|
||||
? default
|
||||
: GetConverter<TResult>(converters).Read(httpResponseMessage, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? Read(Type resultType, HttpResponseMessage httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
GetConverter(resultType, converters).Read(resultType, httpResponseMessage, cancellationToken);
|
||||
public object? Read(Type resultType, HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default) =>
|
||||
httpResponseMessage is null
|
||||
? null
|
||||
: GetConverter(resultType, converters).Read(resultType, httpResponseMessage, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TResult?> ReadAsync<TResult>(HttpResponseMessage httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await GetConverter<TResult>(converters).ReadAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false);
|
||||
public async Task<TResult?> ReadAsync<TResult>(HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return await GetConverter<TResult>(converters).ReadAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await GetConverter(resultType, converters).ReadAsync(resultType, httpResponseMessage, cancellationToken).ConfigureAwait(false);
|
||||
public async Task<object?> ReadAsync(Type resultType, HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await GetConverter(resultType, converters).ReadAsync(resultType, httpResponseMessage, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 <see cref="IHttpContentConverter{TResult}" /> 实例
|
||||
|
@@ -37,7 +37,7 @@ public interface IHttpContentConverterFactory
|
||||
/// <returns>
|
||||
/// <typeparamref name="TResult" />
|
||||
/// </returns>
|
||||
TResult? Read<TResult>(HttpResponseMessage httpResponseMessage,
|
||||
TResult? Read<TResult>(HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +54,7 @@ public interface IHttpContentConverterFactory
|
||||
/// <returns>
|
||||
/// <see cref="object" />
|
||||
/// </returns>
|
||||
object? Read(Type resultType, HttpResponseMessage httpResponseMessage,
|
||||
object? Read(Type resultType, HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -73,7 +73,7 @@ public interface IHttpContentConverterFactory
|
||||
/// <returns>
|
||||
/// <typeparamref name="TResult" />
|
||||
/// </returns>
|
||||
Task<TResult?> ReadAsync<TResult>(HttpResponseMessage httpResponseMessage,
|
||||
Task<TResult?> ReadAsync<TResult>(HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -90,6 +90,6 @@ public interface IHttpContentConverterFactory
|
||||
/// <returns>
|
||||
/// <see cref="object" />
|
||||
/// </returns>
|
||||
Task<object?> ReadAsync(Type resultType, HttpResponseMessage httpResponseMessage,
|
||||
Task<object?> ReadAsync(Type resultType, HttpResponseMessage? httpResponseMessage,
|
||||
IHttpContentConverter[]? converters = null, CancellationToken cancellationToken = default);
|
||||
}
|
@@ -115,6 +115,15 @@ internal sealed class FileDownloadManager
|
||||
var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, HttpCompletionOption.ResponseHeadersRead,
|
||||
cancellationToken);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据文件是否存在及配置的行为来决定是否应继续进行文件下载
|
||||
if (!ShouldContinueWithDownload(httpResponseMessage, out var destinationPath))
|
||||
{
|
||||
@@ -228,6 +237,15 @@ internal sealed class FileDownloadManager
|
||||
var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder,
|
||||
HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据文件是否存在及配置的行为来决定是否应继续进行文件下载
|
||||
if (!ShouldContinueWithDownload(httpResponseMessage, out var destinationPath))
|
||||
{
|
||||
@@ -246,7 +264,7 @@ internal sealed class FileDownloadManager
|
||||
bufferSize, true);
|
||||
|
||||
// 获取 HTTP 响应体中的内容流
|
||||
using var contentStream = (await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false));
|
||||
using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 循环读取数据直到取消请求或读取完毕
|
||||
int numBytesRead;
|
||||
@@ -485,6 +503,9 @@ internal sealed class FileDownloadManager
|
||||
/// </returns>
|
||||
internal string GetFileName(HttpResponseMessage httpResponseMessage)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(httpResponseMessage);
|
||||
|
||||
// 获取文件下载保存的文件的名称
|
||||
var fileName = Path.GetFileName(_httpFileDownloadBuilder.DestinationPath);
|
||||
|
||||
|
@@ -88,7 +88,7 @@ internal sealed class FileUploadManager
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
internal HttpResponseMessage Start(CancellationToken cancellationToken = default)
|
||||
internal HttpResponseMessage? Start(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 创建进度报告任务取消标识
|
||||
using var progressCancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -102,7 +102,7 @@ internal sealed class FileUploadManager
|
||||
// 初始化 Stopwatch 实例并开启计时操作
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
HttpResponseMessage httpResponseMessage;
|
||||
HttpResponseMessage? httpResponseMessage;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -147,7 +147,7 @@ internal sealed class FileUploadManager
|
||||
/// <returns>
|
||||
/// <see cref="Task{TResult}" />
|
||||
/// </returns>
|
||||
internal async Task<HttpResponseMessage> StartAsync(CancellationToken cancellationToken = default)
|
||||
internal async Task<HttpResponseMessage?> StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 创建进度报告任务取消标识
|
||||
using var progressCancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -161,7 +161,7 @@ internal sealed class FileUploadManager
|
||||
// 初始化 Stopwatch 实例并开启计时操作
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
HttpResponseMessage httpResponseMessage;
|
||||
HttpResponseMessage? httpResponseMessage;
|
||||
|
||||
try
|
||||
{
|
||||
|
@@ -97,6 +97,15 @@ internal sealed class LongPollingManager
|
||||
// 发送 HTTP 远程请求
|
||||
var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, cancellationToken);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// 发送响应数据对象到通道
|
||||
dataChannel.Writer.TryWrite(httpResponseMessage);
|
||||
|
||||
@@ -167,6 +176,15 @@ internal sealed class LongPollingManager
|
||||
// 发送 HTTP 远程请求
|
||||
var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// 发送响应数据对象到通道
|
||||
await dataChannel.Writer.WriteAsync(httpResponseMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@@ -109,6 +109,15 @@ internal sealed class ServerSentEventsManager
|
||||
var httpResponseMessage = _httpRemoteService.Send(RequestBuilder, HttpCompletionOption.ResponseHeadersRead,
|
||||
cancellationToken);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 HTTP 响应体中的内容流
|
||||
using var contentStream = httpResponseMessage.Content.ReadAsStream(cancellationToken);
|
||||
|
||||
@@ -203,9 +212,17 @@ internal sealed class ServerSentEventsManager
|
||||
var httpResponseMessage = await _httpRemoteService.SendAsync(RequestBuilder,
|
||||
HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 获取 HTTP 响应体中的内容流
|
||||
using var contentStream = (await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false));
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 HTTP 响应体中的内容流
|
||||
using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
// 初始化 StreamReader 实例
|
||||
using var streamReader = new StreamReader(contentStream, Encoding.UTF8);
|
||||
|
||||
@@ -382,10 +399,10 @@ internal sealed class ServerSentEventsManager
|
||||
? retryInterval
|
||||
: _httpServerSentEventsBuilder.DefaultRetryInterval;
|
||||
break;
|
||||
// 所有其他的字段名都会被忽略
|
||||
// 其他的字段名存储在 CustomFields 属性中
|
||||
default:
|
||||
// 保持数据不变
|
||||
return true;
|
||||
serverSentEventsData.AddCustomField(key, value);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -143,6 +143,15 @@ internal sealed class StressTestHarnessManager
|
||||
var httpResponseMessage =
|
||||
await _httpRemoteService.SendAsync(RequestBuilder, completionOption, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
// 输出调试信息
|
||||
Debugging.Error("The response content was not read, as it was empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查响应状态码是否是成功状态
|
||||
if (httpResponseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
|
@@ -36,7 +36,7 @@ public sealed class HttpRemoteAnalyzer
|
||||
/// <summary>
|
||||
/// 分析数据
|
||||
/// </summary>
|
||||
public string Data => _cachedData ??= _dataBuffer.ToString();
|
||||
public string Data => _cachedData ??= _dataBuffer.ToString().TrimEnd(Environment.NewLine.ToCharArray());
|
||||
|
||||
/// <summary>
|
||||
/// 追加分析数据
|
||||
@@ -44,7 +44,7 @@ public sealed class HttpRemoteAnalyzer
|
||||
/// <param name="value">分析数据</param>
|
||||
internal void AppendData(string? value)
|
||||
{
|
||||
_dataBuffer.Append(value);
|
||||
_dataBuffer.AppendLine(value);
|
||||
_cachedData = null;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,186 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// 提供静态访问 <see cref="IHttpRemoteService" /> 服务的方式
|
||||
/// </summary>
|
||||
/// <remarks>支持服务的延迟初始化、配置更新以及资源释放。</remarks>
|
||||
#pragma warning disable CA1513
|
||||
public static class HttpRemoteClient
|
||||
{
|
||||
/// <inheritdoc cref="IServiceProvider" />
|
||||
internal static IServiceProvider? _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 延迟加载的 <see cref="IHttpRemoteService" /> 实例
|
||||
/// </summary>
|
||||
internal static Lazy<IHttpRemoteService> _lazyService;
|
||||
|
||||
/// <summary>
|
||||
/// 并发锁对象
|
||||
/// </summary>
|
||||
internal static readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 标记服务是否已释放
|
||||
/// </summary>
|
||||
internal static bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义服务注册逻辑的委托
|
||||
/// </summary>
|
||||
internal static Action<IServiceCollection> _configure = services => services.AddHttpRemote();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="HttpRemoteClient" />
|
||||
/// </summary>
|
||||
static HttpRemoteClient() => _lazyService = new Lazy<IHttpRemoteService>(CreateService);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前配置下的 <see cref="IHttpRemoteService" /> 实例
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public static IHttpRemoteService Service
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HttpRemoteClient));
|
||||
}
|
||||
|
||||
return _lazyService.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义服务注册逻辑
|
||||
/// </summary>
|
||||
public static void Configure(Action<IServiceCollection> configure)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_configure = configure;
|
||||
|
||||
// 重新初始化服务
|
||||
Reinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放服务提供器及相关资源
|
||||
/// </summary>
|
||||
/// <remarks>通常在应用程序关闭或不再需要 HTTP 远程请求服务时调用。</remarks>
|
||||
public static void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 释放服务提供器
|
||||
ReleaseServiceProvider();
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 <see cref="IHttpRemoteService" /> 实例
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see cref="IHttpRemoteService" />
|
||||
/// </returns>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
internal static IHttpRemoteService CreateService()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HttpRemoteClient));
|
||||
}
|
||||
|
||||
// 如果值已创建,直接返回
|
||||
if (_lazyService.IsValueCreated)
|
||||
{
|
||||
return _lazyService.Value;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 初始化 ServiceCollection 实例
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// 调用自定义服务注册逻辑的委托
|
||||
_configure(services);
|
||||
|
||||
// 构建服务提供器
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// 解析 IHttpRemoteService 实例
|
||||
var service = _serviceProvider.GetRequiredService<IHttpRemoteService>();
|
||||
|
||||
return service;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to initialize IHttpRemoteService.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用最新的 <see cref="Configure" /> 配置重新初始化服务
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
internal static void Reinitialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HttpRemoteClient));
|
||||
}
|
||||
|
||||
// 释放当前的服务提供器
|
||||
ReleaseServiceProvider();
|
||||
|
||||
// 重新创建延迟加载实例
|
||||
_lazyService = new Lazy<IHttpRemoteService>(CreateService, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放服务提供器
|
||||
/// </summary>
|
||||
internal static void ReleaseServiceProvider()
|
||||
{
|
||||
// 如果服务提供器支持释放资源,则执行释放操作
|
||||
if (_serviceProvider is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_serviceProvider = null;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1513
|
@@ -108,6 +108,80 @@ public sealed class HttpRemoteResult<TResult>
|
||||
/// </summary>
|
||||
public HttpContentHeaders ContentHeaders { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 版本
|
||||
/// </summary>
|
||||
public Version Version { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HttpClient" /> 实例的配置名称
|
||||
/// </summary>
|
||||
public string? HttpClientName { get; private set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 解构函数(至少包含两个 out 参数!!!)
|
||||
// /// </summary>
|
||||
// /// <param name="result">
|
||||
// /// <typeparamref name="TResult" />
|
||||
// /// </param>
|
||||
// public void Deconstruct(out TResult? result)
|
||||
// {
|
||||
// result = Result;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 解构函数
|
||||
/// </summary>
|
||||
/// <param name="result">
|
||||
/// <typeparamref name="TResult" />
|
||||
/// </param>
|
||||
/// <param name="httpResponseMessage">
|
||||
/// <inheritdoc cref="HttpResponseMessage" />
|
||||
/// </param>
|
||||
public void Deconstruct(out TResult? result, out HttpResponseMessage httpResponseMessage)
|
||||
{
|
||||
result = Result;
|
||||
httpResponseMessage = ResponseMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解构函数
|
||||
/// </summary>
|
||||
/// <param name="result">
|
||||
/// <typeparamref name="TResult" />
|
||||
/// </param>
|
||||
/// <param name="httpResponseMessage">
|
||||
/// <inheritdoc cref="HttpResponseMessage" />
|
||||
/// </param>
|
||||
/// <param name="isSuccessStatusCode">是否请求成功</param>
|
||||
public void Deconstruct(out TResult? result, out HttpResponseMessage httpResponseMessage,
|
||||
out bool isSuccessStatusCode)
|
||||
{
|
||||
result = Result;
|
||||
httpResponseMessage = ResponseMessage;
|
||||
isSuccessStatusCode = IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解构函数
|
||||
/// </summary>
|
||||
/// <param name="result">
|
||||
/// <typeparamref name="TResult" />
|
||||
/// </param>
|
||||
/// <param name="httpResponseMessage">
|
||||
/// <inheritdoc cref="HttpResponseMessage" />
|
||||
/// </param>
|
||||
/// <param name="isSuccessStatusCode">是否请求成功</param>
|
||||
/// <param name="statusCode">响应状态码</param>
|
||||
public void Deconstruct(out TResult? result, out HttpResponseMessage httpResponseMessage,
|
||||
out bool isSuccessStatusCode, out HttpStatusCode statusCode)
|
||||
{
|
||||
result = Result;
|
||||
httpResponseMessage = ResponseMessage;
|
||||
isSuccessStatusCode = IsSuccessStatusCode;
|
||||
statusCode = StatusCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
@@ -124,6 +198,16 @@ public sealed class HttpRemoteResult<TResult>
|
||||
|
||||
// 解析响应标头 Set-Cookie 集合
|
||||
ParseSetCookies(ResponseMessage.Headers);
|
||||
|
||||
// 获取 HTTP 版本
|
||||
Version = ResponseMessage.Version;
|
||||
|
||||
// 获取 HttpClient 实例的配置名称
|
||||
if (ResponseMessage.RequestMessage?.Options.TryGetValue(
|
||||
new HttpRequestOptionsKey<string>(Constants.HTTP_CLIENT_NAME), out var httpClientName) == true)
|
||||
{
|
||||
HttpClientName = httpClientName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -9,6 +9,7 @@
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
@@ -19,6 +20,11 @@ namespace ThingsGateway.HttpRemote;
|
||||
/// <remarks>参考文献:https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events/Using_server-sent_events#%E5%AD%97%E6%AE%B5。</remarks>
|
||||
public sealed class ServerSentEventsData
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于存储自定义的字段数据
|
||||
/// </summary>
|
||||
internal readonly List<KeyValuePair<string, string>> _customFields;
|
||||
|
||||
/// <summary>
|
||||
/// 消息数据构建器
|
||||
/// </summary>
|
||||
@@ -32,7 +38,11 @@ public sealed class ServerSentEventsData
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ServerSentEventsData" />
|
||||
/// </summary>
|
||||
internal ServerSentEventsData() => _dataBuffer = new StringBuilder();
|
||||
internal ServerSentEventsData()
|
||||
{
|
||||
_dataBuffer = new StringBuilder();
|
||||
_customFields = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件类型
|
||||
@@ -61,6 +71,12 @@ public sealed class ServerSentEventsData
|
||||
/// <remarks>重新连接的时间。如果与服务器的连接丢失,浏览器将等待指定的时间,然后尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接的时间。如果指定了一个非整数值,该字段将被忽略。</remarks>
|
||||
public int Retry { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义的字段数据
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<KeyValuePair<string, string>> CustomFields =>
|
||||
new ReadOnlyCollection<KeyValuePair<string, string>>(_customFields);
|
||||
|
||||
/// <summary>
|
||||
/// 追加消息数据
|
||||
/// </summary>
|
||||
@@ -70,4 +86,12 @@ public sealed class ServerSentEventsData
|
||||
_dataBuffer.Append(value);
|
||||
_cachedData = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追加自定义字段数据
|
||||
/// </summary>
|
||||
/// <param name="name">字段名</param>
|
||||
/// <param name="value">字段数据</param>
|
||||
internal void AddCustomField(string name, string value) =>
|
||||
_customFields.Add(new KeyValuePair<string, string>(name, value));
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HttpClient" /> 配置选项
|
||||
/// </summary>
|
||||
public sealed class HttpClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON 序列化配置
|
||||
/// </summary>
|
||||
public JsonSerializerOptions JsonSerializerOptions { get; set; } =
|
||||
new(HttpRemoteOptions.JsonSerializerOptionsDefault);
|
||||
|
||||
/// <summary>
|
||||
/// 标识选项是否配置为默认值(未配置)
|
||||
/// </summary>
|
||||
/// <remarks>用于避免通过 <see cref="IOptionsSnapshot{TOptions}" /> 获取选项时无法确定是否已配置该选项。默认值为:<c>true</c>。</remarks>
|
||||
internal bool IsDefault { get; set; } = true;
|
||||
}
|
@@ -12,9 +12,12 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using ThingsGateway.Converters.Json;
|
||||
|
||||
namespace ThingsGateway.HttpRemote;
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +33,18 @@ public sealed class HttpRemoteOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
||||
// 允许 String 转 Number
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
// 解决中文乱码问题
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
AllowTrailingCommas = true,
|
||||
Converters =
|
||||
{
|
||||
new DateTimeConverterUsingDateTimeParseAsFallback(),
|
||||
new DateTimeOffsetConverterUsingDateTimeOffsetParseAsFallback(),
|
||||
// 允许 Number 或 Boolean 转 String
|
||||
new StringJsonConverter()
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@@ -18,6 +18,7 @@ using System.Net.Http.Json;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
|
||||
@@ -52,7 +53,7 @@ public class StringContentProcessor : HttpContentProcessorBase
|
||||
}
|
||||
|
||||
// 将原始请求内容转换为字符串
|
||||
var content = rawContent.GetType().IsBasicType() || rawContent is JsonElement
|
||||
var content = rawContent.GetType().IsBasicType() || rawContent is JsonElement or JsonNode
|
||||
? rawContent.ToCultureString(CultureInfo.InvariantCulture)
|
||||
: JsonSerializer.Serialize(rawContent,
|
||||
ServiceProvider?.GetRequiredService<IOptions<HttpRemoteOptions>>().Value.JsonSerializerOptions ??
|
||||
|
@@ -51,7 +51,7 @@ internal sealed partial class HttpRemoteService
|
||||
new FileDownloadManager(this, httpFileDownloadBuilder, configure).StartAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage UploadFile(string? requestUri, string filePath, string name = "file",
|
||||
public HttpResponseMessage? UploadFile(string? requestUri, string filePath, string name = "file",
|
||||
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
|
||||
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
@@ -60,7 +60,7 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> UploadFileAsync(string? requestUri, string filePath, string name = "file",
|
||||
public Task<HttpResponseMessage?> UploadFileAsync(string? requestUri, string filePath, string name = "file",
|
||||
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
|
||||
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
@@ -69,12 +69,12 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Send(HttpFileUploadBuilder httpFileUploadBuilder,
|
||||
public HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
new FileUploadManager(this, httpFileUploadBuilder, configure).Start(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder,
|
||||
public Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
new FileUploadManager(this, httpFileUploadBuilder, configure).StartAsync(cancellationToken);
|
||||
|
||||
|
@@ -17,22 +17,22 @@ namespace ThingsGateway.HttpRemote;
|
||||
internal sealed partial class HttpRemoteService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Get(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Get(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Get(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Get(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Get(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => GetAsync(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> GetAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> GetAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -58,22 +58,22 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Get<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Get<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
GetAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Get, requestUri, configure), completionOption,
|
||||
cancellationToken);
|
||||
@@ -133,22 +133,22 @@ internal sealed partial class HttpRemoteService
|
||||
GetAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Put(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Put(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Put(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Put(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Put(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => PutAsync(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> PutAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> PutAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -174,22 +174,22 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Put<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Put<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
PutAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Put, requestUri, configure), completionOption,
|
||||
cancellationToken);
|
||||
@@ -249,23 +249,23 @@ internal sealed partial class HttpRemoteService
|
||||
PutAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Post(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Post(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Post(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Post(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Post(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => PostAsync(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> PostAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> PostAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -291,22 +291,23 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Post<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Post<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
PostAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri,
|
||||
HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Post, requestUri, configure), completionOption,
|
||||
cancellationToken);
|
||||
@@ -366,23 +367,23 @@ internal sealed partial class HttpRemoteService
|
||||
PostAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Delete(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Delete(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Delete(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => DeleteAsync(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> DeleteAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> DeleteAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -408,22 +409,22 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Delete<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
DeleteAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri,
|
||||
HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Delete, requestUri, configure), completionOption,
|
||||
@@ -487,23 +488,23 @@ internal sealed partial class HttpRemoteService
|
||||
DeleteAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Head(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Head(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Head(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Head(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Head(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => HeadAsync(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> HeadAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> HeadAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -529,22 +530,23 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Head<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Head<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
HeadAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri,
|
||||
HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Head, requestUri, configure), completionOption,
|
||||
cancellationToken);
|
||||
@@ -604,23 +606,23 @@ internal sealed partial class HttpRemoteService
|
||||
HeadAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Options(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Options(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Options(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Options(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Options(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => OptionsAsync(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> OptionsAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> OptionsAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -646,22 +648,22 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Options<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Options<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
OptionsAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri,
|
||||
HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Options, requestUri, configure), completionOption,
|
||||
@@ -725,23 +727,23 @@ internal sealed partial class HttpRemoteService
|
||||
OptionsAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Trace(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Trace(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Trace(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => TraceAsync(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> TraceAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> TraceAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -767,22 +769,22 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Trace<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
TraceAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri,
|
||||
HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Trace, requestUri, configure), completionOption,
|
||||
@@ -846,23 +848,23 @@ internal sealed partial class HttpRemoteService
|
||||
TraceAsAsync<byte[]>(requestUri, completionOption, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpResponseMessage? Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Patch(requestUri, HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Patch(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Patch(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send(
|
||||
HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public Task<HttpResponseMessage?> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => PatchAsync(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead,
|
||||
configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> PatchAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
public Task<HttpResponseMessage?> PatchAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => SendAsync(
|
||||
HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
@@ -888,22 +890,22 @@ internal sealed partial class HttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
public HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default) => Patch<TResult>(requestUri,
|
||||
HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
public HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => Send<TResult>(
|
||||
HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
PatchAsync<TResult>(requestUri, HttpCompletionOption.ResponseContentRead, configure, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri,
|
||||
public Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri,
|
||||
HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) =>
|
||||
SendAsync<TResult>(HttpRequestBuilder.Create(HttpMethod.Patch, requestUri, configure), completionOption,
|
||||
|
@@ -90,16 +90,16 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder,
|
||||
public HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Send(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
public HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, _) = SendCoreAsync(httpRequestBuilder, completionOption, default,
|
||||
var (httpResponseMessage, _) = SendCoreAsync(httpRequestBuilder, completionOption, null,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
@@ -107,18 +107,18 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder,
|
||||
public Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
SendAsync(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder,
|
||||
public async Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpCompletionOption completionOption, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, _) = await SendCoreAsync(httpRequestBuilder, completionOption,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false);
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return httpResponseMessage;
|
||||
}
|
||||
@@ -133,7 +133,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, default,
|
||||
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, null,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
@@ -156,7 +156,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
cancellationToken);
|
||||
|
||||
// 动态创建 HttpRemoteResult<TResult> 实例并转换为 TResult 实例
|
||||
return (TResult)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration);
|
||||
return (TResult?)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -199,7 +199,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false);
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 获取结果类型
|
||||
var resultType = typeof(TResult);
|
||||
@@ -220,7 +220,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 动态创建 HttpRemoteResult<TResult> 实例并转换为 TResult 实例
|
||||
return (TResult)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration);
|
||||
return (TResult?)DynamicCreateHttpRemoteResult(resultType, httpResponseMessage, result, requestDuration);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -263,7 +263,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, default,
|
||||
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, null,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
@@ -298,7 +298,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false);
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 检查类型是否是 HttpRemoteResult<TResult> 类型
|
||||
if (!typeof(HttpRemoteResult<>).IsDefinitionEqual(resultType))
|
||||
@@ -320,19 +320,25 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
public HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Send<TResult>(httpRequestBuilder, HttpCompletionOption.ResponseContentRead, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
public HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpCompletionOption completionOption, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, default,
|
||||
var (httpResponseMessage, requestDuration) = SendCoreAsync(httpRequestBuilder, completionOption, null,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.Send(httpRequestMessage, option, token), cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 将 HttpResponseMessage 转换为 TResult 实例
|
||||
var result = _httpContentConverterFactory.Read<TResult>(httpResponseMessage,
|
||||
httpRequestBuilder.HttpContentConverterProviders?.SelectMany(u => u.Invoke()).ToArray(), cancellationToken);
|
||||
@@ -348,18 +354,24 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
public Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default) => SendAsync<TResult>(httpRequestBuilder,
|
||||
HttpCompletionOption.ResponseContentRead, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
public async Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpCompletionOption completionOption, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 发送 HTTP 远程请求
|
||||
var (httpResponseMessage, requestDuration) = await SendCoreAsync(httpRequestBuilder, completionOption,
|
||||
(httpClient, httpRequestMessage, option, token) =>
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), default, cancellationToken).ConfigureAwait(false);
|
||||
httpClient.SendAsync(httpRequestMessage, option, token), null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 将 HttpResponseMessage 转换为 TResult 实例
|
||||
var result = await _httpContentConverterFactory.ReadAsync<TResult>(httpResponseMessage,
|
||||
@@ -392,7 +404,8 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="Tuple{T1, T2}" />
|
||||
/// </returns>
|
||||
internal async Task<(HttpResponseMessage ResponseMessage, long RequestDuration)> SendCoreAsync(
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
internal async Task<(HttpResponseMessage? ResponseMessage, long RequestDuration)> SendCoreAsync(
|
||||
HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
Func<HttpClient, HttpRequestMessage, HttpCompletionOption, CancellationToken, Task<HttpResponseMessage>>?
|
||||
sendAsyncMethod,
|
||||
@@ -445,6 +458,20 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
// 设置单次请求超时时间控制
|
||||
if (httpRequestBuilder.Timeout is not null && httpRequestBuilder.Timeout.Value != TimeSpan.Zero)
|
||||
{
|
||||
// 确保 HttpRequestBuilder 的 Timeout 属性值小于 HttpClient 的 Timeout 属性值(默认 100秒)
|
||||
if (httpRequestBuilder.Timeout.Value > httpClient.Timeout)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"HttpRequestBuilder's Timeout cannot be greater than HttpClient's Timeout, which defaults to 100 seconds.");
|
||||
}
|
||||
|
||||
// 调用超时发生时要执行的操作
|
||||
if (httpRequestBuilder.TimeoutAction is not null)
|
||||
{
|
||||
timeoutCancellationTokenSource.Token.Register(httpRequestBuilder.TimeoutAction.TryInvoke);
|
||||
}
|
||||
|
||||
// 延迟指定时间后取消任务
|
||||
timeoutCancellationTokenSource.CancelAfter(httpRequestBuilder.Timeout.Value);
|
||||
}
|
||||
|
||||
@@ -534,7 +561,13 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
// 处理发送 HTTP 请求发生异常
|
||||
HandleRequestFailed(httpRequestBuilder, requestEventHandler, e, httpResponseMessage);
|
||||
|
||||
throw;
|
||||
// 检查是否启用异常抑制机制
|
||||
if (!ShouldSuppressException(httpRequestBuilder.SuppressExceptionTypes, e))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return (httpResponseMessage, requestDuration);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -706,7 +739,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
ArgumentNullException.ThrowIfNull(httpClient);
|
||||
|
||||
// 添加默认的 User-Agent 标头
|
||||
AddDefaultUserAgentHeader(httpClient);
|
||||
AddDefaultUserAgentHeader(httpClient, httpRequestBuilder);
|
||||
|
||||
// 存储 HttpClientPooling 实例并返回
|
||||
return httpRequestBuilder.HttpClientPooling = new HttpClientPooling(httpClient, release);
|
||||
@@ -719,10 +752,15 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
/// <param name="httpClient">
|
||||
/// <see cref="HttpClient" />
|
||||
/// </param>
|
||||
internal static void AddDefaultUserAgentHeader(HttpClient httpClient)
|
||||
/// <param name="httpRequestBuilder">
|
||||
/// <see cref="HttpRequestBuilder" />
|
||||
/// </param>
|
||||
internal static void AddDefaultUserAgentHeader(HttpClient httpClient, HttpRequestBuilder httpRequestBuilder)
|
||||
{
|
||||
// 空检查
|
||||
if (httpClient.DefaultRequestHeaders.UserAgent.Count != 0)
|
||||
if (httpClient.DefaultRequestHeaders.UserAgent.Count != 0 ||
|
||||
httpRequestBuilder.HeadersToRemove?.Contains(HeaderNames.UserAgent) == true ||
|
||||
httpRequestBuilder.Headers?.ContainsKey(HeaderNames.UserAgent) == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -854,10 +892,10 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
int.TryParse(stringStatusCode, out var intStatusCodeResult) &&
|
||||
intStatusCodeResult == statusCode:
|
||||
return true;
|
||||
// 处理字符串区间类型,如 200-500
|
||||
// 处理字符串区间类型,如 200-500 或 200~500
|
||||
case string stringStatusCode when StatusCodeRangeRegex().IsMatch(stringStatusCode):
|
||||
// 根据 - 符号切割
|
||||
var parts = stringStatusCode.Split('-', StringSplitOptions.RemoveEmptyEntries);
|
||||
// 根据 - 或 ~ 符号切割
|
||||
var parts = stringStatusCode.Split(['-', '~'], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 比较状态码区间
|
||||
if (parts.Length == 2 && int.TryParse(parts[0], out var start) && int.TryParse(parts[1], out var end))
|
||||
@@ -888,8 +926,6 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
"=" => statusCode == number,
|
||||
_ => false
|
||||
};
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -908,9 +944,8 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
/// <see cref="object" />
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
internal static object DynamicCreateHttpRemoteResult(Type httpRemoteResultType,
|
||||
HttpResponseMessage httpResponseMessage,
|
||||
object? result, long requestDuration)
|
||||
internal static object? DynamicCreateHttpRemoteResult(Type httpRemoteResultType,
|
||||
HttpResponseMessage? httpResponseMessage, object? result, long requestDuration)
|
||||
{
|
||||
// 检查类型是否是 HttpRemoteResult<TResult> 类型
|
||||
if (!typeof(HttpRemoteResult<>).IsDefinitionEqual(httpRemoteResultType))
|
||||
@@ -920,6 +955,12 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
nameof(httpRemoteResultType));
|
||||
}
|
||||
|
||||
// 空检查
|
||||
if (httpResponseMessage is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 反射创建 HttpRemoteResult<TResult> 实例
|
||||
var httpRemoteResult = Activator.CreateInstance(httpRemoteResultType, httpResponseMessage);
|
||||
|
||||
@@ -946,11 +987,32 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService
|
||||
return httpRemoteResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否启用异常抑制机制
|
||||
/// </summary>
|
||||
/// <param name="suppressExceptionTypes">受抑制的异常类型列表</param>
|
||||
/// <param name="exception">
|
||||
/// <see cref="Exception" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
internal static bool ShouldSuppressException(HashSet<Type>? suppressExceptionTypes, Exception? exception)
|
||||
{
|
||||
// 空检查
|
||||
if (suppressExceptionTypes is null or { Count: 0 } || exception is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return suppressExceptionTypes.Any(u => u.IsInstanceOfType(exception));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态码区间正则表达式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[GeneratedRegex(@"^\d+-\d+$")]
|
||||
[GeneratedRegex(@"^\d+[-~]\d+$")]
|
||||
private static partial Regex StatusCodeRangeRegex();
|
||||
|
||||
/// <summary>
|
||||
|
@@ -106,7 +106,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage UploadFile(string? requestUri, string filePath, string name = "file",
|
||||
HttpResponseMessage? UploadFile(string? requestUri, string filePath, string name = "file",
|
||||
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
|
||||
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
@@ -127,7 +127,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="Task{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> UploadFileAsync(string? requestUri, string filePath, string name = "file",
|
||||
Task<HttpResponseMessage?> UploadFileAsync(string? requestUri, string filePath, string name = "file",
|
||||
Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null,
|
||||
Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
@@ -145,7 +145,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Send(HttpFileUploadBuilder httpFileUploadBuilder, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -161,7 +161,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="Task{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder,
|
||||
Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
@@ -27,7 +27,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Get(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Get(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +44,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Get(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Get(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -58,7 +58,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> GetAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -75,7 +75,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> GetAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> GetAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +156,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -174,7 +174,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Get<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Get<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -189,7 +189,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -207,7 +207,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> GetAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -407,7 +407,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Put(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Put(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -424,7 +424,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Put(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Put(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -438,7 +438,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> PutAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -455,7 +455,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> PutAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> PutAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -536,7 +536,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -554,7 +554,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Put<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Put<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -569,7 +569,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -587,7 +587,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> PutAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -787,7 +787,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Post(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Post(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -804,7 +804,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Post(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Post(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -818,7 +818,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> PostAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -835,7 +835,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> PostAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> PostAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -916,7 +916,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -934,7 +934,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Post<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Post<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -949,7 +949,8 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -967,7 +968,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> PostAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> PostAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1167,7 +1168,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Delete(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1184,7 +1185,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Delete(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Delete(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1198,7 +1199,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> DeleteAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1215,7 +1216,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> DeleteAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> DeleteAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1296,7 +1297,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1314,7 +1315,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Delete<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Delete<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1329,7 +1330,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri,
|
||||
Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -1348,7 +1349,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> DeleteAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> DeleteAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1548,7 +1549,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Head(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Head(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1565,7 +1566,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Head(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Head(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1579,7 +1580,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> HeadAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1596,7 +1597,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> HeadAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> HeadAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1677,7 +1678,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1695,7 +1696,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Head<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Head<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1710,7 +1711,8 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1728,7 +1730,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> HeadAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> HeadAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1928,7 +1930,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Options(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Options(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1945,7 +1947,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Options(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Options(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1959,7 +1961,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> OptionsAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -1976,7 +1978,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> OptionsAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> OptionsAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2057,7 +2059,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2075,7 +2077,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Options<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Options<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2090,7 +2092,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri,
|
||||
Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -2109,7 +2111,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> OptionsAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> OptionsAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2309,7 +2311,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Trace(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2326,7 +2328,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Trace(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Trace(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2340,7 +2342,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> TraceAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2357,7 +2359,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> TraceAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> TraceAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2438,7 +2440,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2456,7 +2458,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Trace<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Trace<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2471,7 +2473,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri,
|
||||
Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -2490,7 +2492,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> TraceAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> TraceAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2690,7 +2692,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpResponseMessage? Patch(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2707,7 +2709,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Patch(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Patch(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2721,7 +2723,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
Task<HttpResponseMessage?> PatchAsync(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2738,7 +2740,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> PatchAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> PatchAsync(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2819,7 +2821,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2837,7 +2839,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Patch<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
HttpRemoteResult<TResult>? Patch<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -2852,7 +2854,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri,
|
||||
Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri,
|
||||
Action<HttpRequestBuilder>? configure = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -2871,7 +2873,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> PatchAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Task<HttpRemoteResult<TResult>?> PatchAsync<TResult>(string? requestUri, HttpCompletionOption completionOption,
|
||||
Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
@@ -33,7 +33,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, CancellationToken cancellationToken = default);
|
||||
HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发送 HTTP 远程请求
|
||||
@@ -50,7 +50,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
HttpResponseMessage Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
HttpResponseMessage? Send(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -65,7 +65,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder,
|
||||
Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -83,7 +83,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpResponseMessage" />
|
||||
/// </returns>
|
||||
Task<HttpResponseMessage> SendAsync(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
Task<HttpResponseMessage?> SendAsync(HttpRequestBuilder httpRequestBuilder, HttpCompletionOption completionOption,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -433,7 +433,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -452,7 +452,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
HttpRemoteResult<TResult> Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpRemoteResult<TResult>? Send<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpCompletionOption completionOption, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -468,7 +468,7 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -487,6 +487,6 @@ public partial interface IHttpRemoteService
|
||||
/// <returns>
|
||||
/// <see cref="HttpRemoteResult{TResult}" />
|
||||
/// </returns>
|
||||
Task<HttpRemoteResult<TResult>> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
Task<HttpRemoteResult<TResult>?> SendAsync<TResult>(HttpRequestBuilder httpRequestBuilder,
|
||||
HttpCompletionOption completionOption, CancellationToken cancellationToken = default);
|
||||
}
|
@@ -41,7 +41,7 @@ public static class HttpRemoteUtility
|
||||
/// 忽略 SSL 证书验证
|
||||
/// </summary>
|
||||
public static Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool> IgnoreSslErrors =>
|
||||
(message, cert, chain, errors) => true;
|
||||
(_, _, _, _) => true;
|
||||
|
||||
/// <summary>
|
||||
/// 获取使用 IPv4 连接到服务器的回调
|
||||
@@ -118,8 +118,20 @@ public static class HttpRemoteUtility
|
||||
// - IPv4: AddressFamily.InterNetwork
|
||||
// - IPv6: AddressFamily.InterNetworkV6
|
||||
// - IPv4 或 IPv6: AddressFamily.Unspecified
|
||||
// 注意:当主机没有 IP 地址时,此方法会抛出一个 SocketException 异常
|
||||
var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, addressFamily, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
IPAddress[] addresses;
|
||||
|
||||
// 当主机是一个 IP 地址,无需进一步解析
|
||||
if (IPAddress.TryParse(context.DnsEndPoint.Host, out var ipAddress))
|
||||
{
|
||||
addresses = [ipAddress];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 注意:当主机没有 IP 地址时,此方法会抛出一个 SocketException 异常
|
||||
var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, addressFamily, cancellationToken).ConfigureAwait(false);
|
||||
addresses = entry.AddressList;
|
||||
}
|
||||
|
||||
// 打开与目标主机/端口的连接
|
||||
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
@@ -129,7 +141,7 @@ public static class HttpRemoteUtility
|
||||
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(entry.AddressList, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false);
|
||||
await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 如果你想选择特定的 IP 地址来连接服务器
|
||||
// await socket.ConnectAsync(
|
||||
|
@@ -34,5 +34,5 @@ public sealed class WebSocketBinaryReceiveResult : WebSocketReceiveResult
|
||||
/// <summary>
|
||||
/// 二进制消息
|
||||
/// </summary>
|
||||
public byte[] Message { get; internal init; } = default!;
|
||||
public byte[] Message { get; internal init; } = null!;
|
||||
}
|
@@ -34,5 +34,5 @@ public sealed class WebSocketTextReceiveResult : WebSocketReceiveResult
|
||||
/// <summary>
|
||||
/// 文本消息
|
||||
/// </summary>
|
||||
public string Message { get; internal init; } = default!;
|
||||
public string Message { get; internal init; } = null!;
|
||||
}
|
@@ -329,6 +329,7 @@ public class FallbackPolicy<TResult> : PolicyBase<TResult>
|
||||
{
|
||||
// 获取操作方法执行结果
|
||||
context.Result = await operation(cancellationToken).ConfigureAwait(false);
|
||||
context.Exception = null;
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
|
@@ -391,6 +391,7 @@ public class RetryPolicy<TResult> : PolicyBase<TResult>
|
||||
{
|
||||
// 获取操作方法执行结果
|
||||
context.Result = await operation(cancellationToken).ConfigureAwait(false);
|
||||
context.Exception = null;
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
|
@@ -0,0 +1,19 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Shapeless;
|
||||
|
||||
/// <summary>
|
||||
/// 流变对象模型绑定特性
|
||||
/// </summary>
|
||||
/// <remarks>示例代码:<c>[Clay] dynamic input</c>。</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class ClayAttribute : Attribute;
|
@@ -14,7 +14,12 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
|
||||
using ThingsGateway.Extensions;
|
||||
|
||||
namespace ThingsGateway.Shapeless;
|
||||
|
||||
@@ -35,9 +40,13 @@ internal sealed class ClayBinder(IOptions<ClayOptions> options) : IModelBinder
|
||||
// 获取 HttpContext 实例
|
||||
var httpContext = bindingContext.HttpContext;
|
||||
|
||||
// 检查是否是 URL 表单(application/x-www-form-urlencoded)内容
|
||||
var isFormUrlEncoded = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType!).MediaType ==
|
||||
MediaTypeNames.Application.FormUrlEncoded;
|
||||
|
||||
// 尝试从请求体中读取数据,并将其转换为 Clay 实例
|
||||
var (canParse, model) =
|
||||
await TryReadAndConvertBodyToClayAsync(httpContext.Request.Body, options.Value, httpContext.RequestAborted).ConfigureAwait(false);
|
||||
var (canParse, model) = await TryReadAndConvertBodyToClayAsync(httpContext.Request.Body, options.Value,
|
||||
isFormUrlEncoded, httpContext.RequestAborted).ConfigureAwait(false);
|
||||
|
||||
bindingContext.Result = !canParse ? ModelBindingResult.Failed() : ModelBindingResult.Success(model);
|
||||
}
|
||||
@@ -49,6 +58,7 @@ internal sealed class ClayBinder(IOptions<ClayOptions> options) : IModelBinder
|
||||
/// <param name="options">
|
||||
/// <see cref="ClayOptions" />
|
||||
/// </param>
|
||||
/// <param name="isFormUrlEncoded">是否是 <c>application/x-www-form-urlencoded</c> 表单</param>
|
||||
/// <param name="cancellationToken">
|
||||
/// <see cref="CancellationToken" />
|
||||
/// </param>
|
||||
@@ -56,7 +66,7 @@ internal sealed class ClayBinder(IOptions<ClayOptions> options) : IModelBinder
|
||||
/// <see cref="Tuple{T1,T2}" />
|
||||
/// </returns>
|
||||
internal static async Task<(bool canParse, Clay? model)> TryReadAndConvertBodyToClayAsync(Stream stream,
|
||||
ClayOptions options, CancellationToken cancellationToken)
|
||||
ClayOptions options, bool isFormUrlEncoded, CancellationToken cancellationToken)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
@@ -65,7 +75,11 @@ internal sealed class ClayBinder(IOptions<ClayOptions> options) : IModelBinder
|
||||
using var streamReader = new StreamReader(stream);
|
||||
var json = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return string.IsNullOrEmpty(json) ? (false, null) : (true, Clay.Parse(json, options));
|
||||
return string.IsNullOrEmpty(json)
|
||||
? (false, null)
|
||||
: (true,
|
||||
Clay.Parse(isFormUrlEncoded ? HttpUtility.UrlDecode(json).ParseFormatKeyValueString(['&'], '?') : json,
|
||||
options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,9 +103,13 @@ internal sealed class ClayBinder(IOptions<ClayOptions> options) : IModelBinder
|
||||
// 解析 ClayOptions 选项
|
||||
var options = httpContext.RequestServices.GetRequiredService<IOptions<ClayOptions>>().Value;
|
||||
|
||||
// 检查是否是 URL 表单(application/x-www-form-urlencoded)内容
|
||||
var isFormUrlEncoded = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType!).MediaType ==
|
||||
MediaTypeNames.Application.FormUrlEncoded;
|
||||
|
||||
// 尝试从请求体流中读取数据,并将其转换为 Clay 实例
|
||||
var (_, model) =
|
||||
await TryReadAndConvertBodyToClayAsync(httpContext.Request.Body, options, httpContext.RequestAborted).ConfigureAwait(false);
|
||||
var (_, model) = await TryReadAndConvertBodyToClayAsync(httpContext.Request.Body, options, isFormUrlEncoded,
|
||||
httpContext.RequestAborted).ConfigureAwait(false);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@@ -11,6 +11,9 @@
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ThingsGateway.Shapeless;
|
||||
|
||||
@@ -25,6 +28,15 @@ internal sealed class ClayBinderProvider : IModelBinderProvider
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
return context.Metadata.ModelType == typeof(Clay) ? new BinderTypeModelBinder(typeof(ClayBinder)) : null;
|
||||
// 获取模型类型和参数特性列表
|
||||
var modelType = context.Metadata.ModelType;
|
||||
var parameterAttributes = (context.Metadata as DefaultModelMetadata)?.Attributes.ParameterAttributes;
|
||||
|
||||
return modelType == typeof(Clay) ||
|
||||
// 确保参数类型为 dynamic 且贴有 [Clay] 特性
|
||||
(modelType == typeof(object) && parameterAttributes?.OfType<ClayAttribute>().Any() == true &&
|
||||
parameterAttributes.OfType<DynamicAttribute>().Any())
|
||||
? new BinderTypeModelBinder(typeof(ClayBinder))
|
||||
: null;
|
||||
}
|
||||
}
|
@@ -37,6 +37,11 @@ public partial class Clay
|
||||
/// </summary>
|
||||
public IEnumerable<object> Keys => AsEnumerable().Select(u => u.Key);
|
||||
|
||||
/// <summary>
|
||||
/// 获取单一对象键(属性名)的列表
|
||||
/// </summary>
|
||||
public IEnumerable<string> MemberNames => AsEnumerateObject().Select(u => u.Key);
|
||||
|
||||
/// <summary>
|
||||
/// 获取值或元素的列表
|
||||
/// </summary>
|
||||
@@ -127,6 +132,17 @@ public partial class Clay
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将流变对象转换为 <see cref="Dictionary{TKey,TValue}" />
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see cref="Dictionary{TKey,TValue}" />
|
||||
/// </returns>
|
||||
public Dictionary<string, dynamic?> ToDictionary() =>
|
||||
IsObject
|
||||
? As<Dictionary<string, dynamic?>>()!
|
||||
: As<Dictionary<int, dynamic?>>()!.ToDictionary(u => u.Key.ToString(), u => u.Value);
|
||||
|
||||
/// <summary>
|
||||
/// 遍历 <see cref="Clay" />
|
||||
/// </summary>
|
||||
|
@@ -10,6 +10,7 @@
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
@@ -86,6 +87,14 @@ public partial class Clay
|
||||
/// </param>
|
||||
public Clay this[Range range] => (Clay)this[range as object]!;
|
||||
|
||||
/// <summary>
|
||||
/// 路径索引
|
||||
/// </summary>
|
||||
/// <remarks>根据路径获取值。</remarks>
|
||||
/// <param name="identifier">带路径的标识符</param>
|
||||
/// <param name="isPath">是否是带路径的标识符</param>
|
||||
public object? this[string identifier, bool isPath] => isPath ? PathValue(identifier) : GetValue(identifier);
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为单一对象
|
||||
/// </summary>
|
||||
@@ -155,6 +164,35 @@ public partial class Clay
|
||||
return ToJsonString(jsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解构函数
|
||||
/// </summary>
|
||||
/// <param name="clay">dynamic 类型的 <see cref="Clay" /></param>
|
||||
/// <param name="enumerableClay">
|
||||
/// <see cref="IEnumerable{T}" />
|
||||
/// </param>
|
||||
public void Deconstruct(out dynamic clay, out IEnumerable<dynamic?> enumerableClay)
|
||||
{
|
||||
clay = this;
|
||||
enumerableClay = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="clay">dynamic 类型的 <see cref="Clay" /></param>
|
||||
/// <param name="enumerableClay">
|
||||
/// <see cref="IEnumerable{T}" />
|
||||
/// </param>
|
||||
/// <param name="rawClay">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
public void Deconstruct(out dynamic clay, out IEnumerable<dynamic?> enumerableClay, out Clay rawClay)
|
||||
{
|
||||
clay = this;
|
||||
enumerableClay = this;
|
||||
rawClay = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建空的单一对象
|
||||
/// </summary>
|
||||
@@ -260,6 +298,38 @@ public partial class Clay
|
||||
public static Clay Parse(ref Utf8JsonReader utf8JsonReader, Action<ClayOptions> configure) =>
|
||||
Parse(ref utf8JsonReader, ClayOptions.Default.Configure(configure));
|
||||
|
||||
/// <summary>
|
||||
/// 从文件中读取数据并转换为 <see cref="Clay" /> 实例
|
||||
/// </summary>
|
||||
/// <param name="path">文件路径</param>
|
||||
/// <param name="options">
|
||||
/// <see cref="ClayOptions" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
public static Clay ParseFromFile(string path, ClayOptions? options = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
||||
|
||||
// 打开文件并读取流
|
||||
using var fileStream = File.OpenRead(path);
|
||||
|
||||
return Parse(fileStream, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件中读取数据并转换为 <see cref="Clay" /> 实例
|
||||
/// </summary>
|
||||
/// <param name="path">文件路径</param>
|
||||
/// <param name="configure">自定义配置委托</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
public static Clay ParseFromFile(string path, Action<ClayOptions> configure) =>
|
||||
ParseFromFile(path, ClayOptions.Default.Configure(configure));
|
||||
|
||||
/// <summary>
|
||||
/// 检查标识符是否定义
|
||||
/// </summary>
|
||||
@@ -318,6 +388,37 @@ public partial class Clay
|
||||
/// </returns>
|
||||
public bool IsDefined(object identifier) => Contains(identifier);
|
||||
|
||||
/// <summary>
|
||||
/// 检查属性(键)是否定义
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名(键)</param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
public bool HasProperty(string propertyName)
|
||||
{
|
||||
// 检查是否是集合或数组实例调用
|
||||
ThrowIfMethodCalledOnArrayCollection(nameof(HasProperty));
|
||||
|
||||
return Contains(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取集合或数组中指定项(元素)的索引
|
||||
/// </summary>
|
||||
/// <param name="value">项(元素)</param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
public int IndexOf(object? value)
|
||||
{
|
||||
// 检查是否是单一对象实例调用
|
||||
ThrowIfMethodCalledOnSingleObject(nameof(IndexOf));
|
||||
|
||||
return Values.Select((item, index) => new { item, index }).FirstOrDefault(x => object.Equals(x.item, value))
|
||||
?.index ?? -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据标识符获取值
|
||||
/// </summary>
|
||||
@@ -375,6 +476,12 @@ public partial class Clay
|
||||
// 根据标识符查找 JsonNode 节点
|
||||
var jsonNode = FindNode(identifier);
|
||||
|
||||
// 处理 object 类型生成 JsonElement 问题
|
||||
if (resultType == typeof(object))
|
||||
{
|
||||
return DeserializeNode(jsonNode, Options);
|
||||
}
|
||||
|
||||
return IsClay(resultType)
|
||||
? new Clay(jsonNode, Options)
|
||||
: Helpers.DeserializeNode(jsonNode, resultType, jsonSerializerOptions ?? Options.JsonSerializerOptions);
|
||||
@@ -394,6 +501,94 @@ public partial class Clay
|
||||
public TResult? Get<TResult>(object identifier, JsonSerializerOptions? jsonSerializerOptions = null) =>
|
||||
(TResult?)Get(identifier, typeof(TResult), jsonSerializerOptions);
|
||||
|
||||
/// <summary>
|
||||
/// 根据路径获取值
|
||||
/// </summary>
|
||||
/// <remarks>不支持获取自定义委托。</remarks>
|
||||
/// <param name="path">带路径的标识符</param>
|
||||
/// <returns>
|
||||
/// <see cref="object" />
|
||||
/// </returns>
|
||||
public object? PathValue(string path) => PathValue<object>(path);
|
||||
|
||||
/// <summary>
|
||||
/// 根据路径获取值
|
||||
/// </summary>
|
||||
/// <remarks>不支持获取自定义委托。</remarks>
|
||||
/// <param name="path">带路径的标识符</param>
|
||||
/// <param name="resultType">转换的目标类型</param>
|
||||
/// <param name="jsonSerializerOptions">
|
||||
/// <see cref="JsonSerializerOptions" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="object" />
|
||||
/// </returns>
|
||||
public object? PathValue(string path, Type resultType, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
|
||||
// 根据路径分隔符进行分割,并确保至少有一个标识符
|
||||
var identifiers = path.Split(Options.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (identifiers is { Length: 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据标识符查找 JsonNode 节点
|
||||
var currentNode = FindNode(identifiers[0]);
|
||||
if (currentNode is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 遍历剩余的标识符
|
||||
for (var i = 1; i < identifiers.Length; i++)
|
||||
{
|
||||
// 将 currentNode 转换为对象实例
|
||||
var currentValue = DeserializeNode(currentNode, Options);
|
||||
|
||||
// 检查是否是 Clay 类型
|
||||
if (!IsClay(currentValue))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The identifier `{identifiers[i - 1]}` at path `{identifiers[i - 1]}:{identifiers[i]}` does not support further lookup.");
|
||||
}
|
||||
|
||||
// 进行下一级查找
|
||||
currentNode = ((Clay?)currentValue)?.FindNode(identifiers[i]);
|
||||
if (currentNode is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 object 类型生成 JsonElement 问题
|
||||
if (resultType == typeof(object))
|
||||
{
|
||||
return DeserializeNode(currentNode, Options);
|
||||
}
|
||||
|
||||
return IsClay(resultType)
|
||||
? new Clay(currentNode, Options)
|
||||
: Helpers.DeserializeNode(currentNode, resultType, jsonSerializerOptions ?? Options.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据路径获取值
|
||||
/// </summary>
|
||||
/// <remarks>不支持获取自定义委托。</remarks>
|
||||
/// <param name="path">带路径的标识符</param>
|
||||
/// <param name="jsonSerializerOptions">
|
||||
/// <see cref="JsonSerializerOptions" />
|
||||
/// </param>
|
||||
/// <typeparam name="TResult">转换的目标类型</typeparam>
|
||||
/// <returns>
|
||||
/// <typeparamref name="TResult" />
|
||||
/// </returns>
|
||||
public TResult? PathValue<TResult>(string path, JsonSerializerOptions? jsonSerializerOptions = null) =>
|
||||
(TResult?)PathValue(path, typeof(TResult), jsonSerializerOptions);
|
||||
|
||||
/// <summary>
|
||||
/// 根据标识符查找 <see cref="JsonNode" /> 节点
|
||||
/// </summary>
|
||||
@@ -641,7 +836,7 @@ public partial class Clay
|
||||
throw new ArgumentException("Clay array contains one or more null elements.", nameof(clays));
|
||||
}
|
||||
|
||||
// 检查是流变对象类型是否一致
|
||||
// 检查流变对象类型是否一致
|
||||
if (clays.Any(u => u.Type != Type))
|
||||
{
|
||||
throw new InvalidOperationException("All Clay objects must be of the same type.");
|
||||
@@ -668,6 +863,51 @@ public partial class Clay
|
||||
return combineClay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拓展属性或项
|
||||
/// </summary>
|
||||
/// <param name="values">值集合</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public Clay Extend(params object?[] values)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(values);
|
||||
|
||||
// 检查是否是集合或数组
|
||||
if (IsArray)
|
||||
{
|
||||
AddRange(values);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// 遍历所有值
|
||||
foreach (var item in values)
|
||||
{
|
||||
// 检查值是否为空值或基本类型的值
|
||||
if (item is null || item.GetType().IsBasicType())
|
||||
{
|
||||
throw new InvalidOperationException("Cannot extend a single object with null or basic type values.");
|
||||
}
|
||||
|
||||
// 将对象转换为字典集合
|
||||
var dictionary = item is Clay clayItem
|
||||
? clayItem.AsEnumerateObject().ToDictionary(object (u) => u.Key, u => u.Value)
|
||||
: item.ObjectToDictionary();
|
||||
|
||||
// 遍历字典键值并设置
|
||||
foreach (var (key, value) in dictionary!)
|
||||
{
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据标识符删除数据
|
||||
/// </summary>
|
||||
@@ -733,8 +973,8 @@ public partial class Clay
|
||||
/// </returns>
|
||||
public object? As(Type resultType, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
{
|
||||
// 检查是否是 Clay 类型或 IEnumerable<dynamic?> 类型
|
||||
if (IsClay(resultType) || resultType == typeof(IEnumerable<dynamic?>))
|
||||
// 检查是否是 Clay 类型或 IEnumerable<dynamic?> 类型或 object 类型
|
||||
if (IsClay(resultType) || resultType == typeof(IEnumerable<dynamic?>) || resultType == typeof(object))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
@@ -746,15 +986,23 @@ public partial class Clay
|
||||
}
|
||||
|
||||
// 检查是否是 IEnumerable<KeyValuePair<string, dynamic?>> 类型且是单一对象
|
||||
if (resultType == typeof(IEnumerable<KeyValuePair<string, dynamic?>>) && IsObject)
|
||||
if (typeof(IEnumerable<KeyValuePair<string, dynamic?>>).IsAssignableFrom(resultType) && IsObject)
|
||||
{
|
||||
return AsEnumerateObject();
|
||||
return resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
|
||||
? AsEnumerateObject().ToDictionary(u => u.Key, u => u.Value)
|
||||
: AsEnumerateObject();
|
||||
}
|
||||
|
||||
// 检查是否是 IEnumerable<KeyValuePair<int, dynamic?>> 类型且是集合或数组
|
||||
if (resultType == typeof(IEnumerable<KeyValuePair<int, dynamic?>>) && IsArray)
|
||||
if (typeof(IEnumerable<KeyValuePair<int, dynamic?>>).IsAssignableFrom(resultType) && IsArray)
|
||||
{
|
||||
return AsEnumerateArray().Select((item, index) => new KeyValuePair<int, dynamic?>(index, item));
|
||||
// 将流变对象转换为键值对集合
|
||||
var keyValuePairs =
|
||||
AsEnumerateArray().Select((item, index) => new KeyValuePair<int, dynamic?>(index, item));
|
||||
|
||||
return resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
|
||||
? keyValuePairs.ToDictionary(u => u.Key, u => u.Value)
|
||||
: keyValuePairs;
|
||||
}
|
||||
|
||||
// 检查是否是 IActionResult 类型
|
||||
@@ -926,7 +1174,8 @@ public partial class Clay
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
public static bool IsClay(object? obj) => obj is not null && IsClay(obj as Type ?? obj.GetType());
|
||||
public static bool IsClay([NotNullWhen(true)] object? obj) =>
|
||||
obj is not null && IsClay(obj as Type ?? obj.GetType());
|
||||
|
||||
/// <summary>
|
||||
/// 按照键升序排序并返回新的 <see cref="Clay" />
|
||||
@@ -945,7 +1194,7 @@ public partial class Clay
|
||||
|
||||
// 初始化升序排序字典
|
||||
var sorted =
|
||||
new SortedDictionary<string, JsonNode?>(JsonCanvas.AsObject().ToDictionary());
|
||||
new SortedDictionary<string, JsonNode?>(JsonCanvas.AsObject().ToDictionary(), StringComparer.Ordinal);
|
||||
|
||||
return Parse(sorted, options);
|
||||
}
|
||||
@@ -969,7 +1218,7 @@ public partial class Clay
|
||||
// 初始化降序排序字典
|
||||
var sortedDesc =
|
||||
new SortedDictionary<string, JsonNode?>(Comparer<string>.Create((x, y) =>
|
||||
string.Compare(y, x, StringComparison.InvariantCulture)));
|
||||
string.Compare(y, x, StringComparison.Ordinal)));
|
||||
|
||||
// 将 JsonCanvas 转换为 JsonObject 实例
|
||||
var jsonObject = JsonCanvas.AsObject();
|
||||
@@ -1031,6 +1280,64 @@ public partial class Clay
|
||||
return Rebuilt(Options.Configure(configure));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字符串是否是 JSON 对象({})或数组([])
|
||||
/// </summary>
|
||||
/// <param name="input">字符串</param>
|
||||
/// <param name="allowTrailingCommas">是否允许末尾多余逗号。默认值为:<c>false</c>。</param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
public static bool IsJsonObjectOrArray(string? input, bool allowTrailingCommas = false)
|
||||
{
|
||||
// 检查输入是否为字符串类型,且字符串不是由空白字符组成
|
||||
if (input is null || string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 去除字符串两端空格
|
||||
var text = input.Trim();
|
||||
|
||||
// 检查字符串是否以 '{' 开头和 '}' 结尾,或者以 '[' 开头和 ']' 结尾
|
||||
if ((!text.StartsWith('{') || !text.EndsWith('}')) && (!text.StartsWith('[') || !text.EndsWith(']')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 使用 JsonDocument 解析字符串,若解析成功,说明是一个有效的 JSON 格式
|
||||
using var jsonDocument = JsonDocument.Parse(text,
|
||||
new JsonDocumentOptions { AllowTrailingCommas = allowTrailingCommas });
|
||||
|
||||
return jsonDocument.RootElement.ValueKind is JsonValueKind.Object or JsonValueKind.Array;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 <see cref="Clay" /> 实例通过转换管道传递并返回新的 <see cref="Clay" />(失败时抛出异常)
|
||||
/// </summary>
|
||||
/// <param name="transformer">转换函数</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public Clay Pipe(Func<dynamic, dynamic?> transformer) => ExecuteTransformation(transformer, true);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将 <see cref="Clay" /> 实例通过转换管道传递,失败时返回原始对象
|
||||
/// </summary>
|
||||
/// <param name="transformer">转换函数</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
public Clay PipeTry(Func<dynamic, dynamic?> transformer) => ExecuteTransformation(transformer, false);
|
||||
|
||||
/// <summary>
|
||||
/// 单一对象
|
||||
/// </summary>
|
||||
|
@@ -0,0 +1,147 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Shapeless;
|
||||
|
||||
/// <summary>
|
||||
/// 流变对象
|
||||
/// </summary>
|
||||
public partial class Clay
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool Equals(Clay? other)
|
||||
{
|
||||
// 检查是否是相同的实例
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 空检查及基础类型检查
|
||||
if (other is null || Type != other.Type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsObject ? AreObjectEqual(this, other) : AreArrayEqual(this, other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || Equals(obj as Clay);
|
||||
|
||||
/// <summary>
|
||||
/// 重载 == 运算符
|
||||
/// </summary>
|
||||
/// <param name="left">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <param name="right">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
public static bool operator ==(Clay? left, Clay? right) => Equals(left, right);
|
||||
|
||||
/// <summary>
|
||||
/// 重载 != 运算符
|
||||
/// </summary>
|
||||
/// <param name="left">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <param name="right">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
public static bool operator !=(Clay? left, Clay? right) => !(left == right);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// 初始化 HashCode 实例
|
||||
var hash = new HashCode();
|
||||
|
||||
if (IsObject)
|
||||
{
|
||||
// 预处理键值对(排序)
|
||||
var sortedEntries = AsEnumerateObject().OrderBy(kvp => kvp.Key, StringComparer.Ordinal);
|
||||
|
||||
// 遍历键值对集合
|
||||
foreach (var (key, value) in sortedEntries)
|
||||
{
|
||||
// 递归计算键和值的哈希码
|
||||
hash.Add(key?.GetHashCode() ?? 0);
|
||||
hash.Add(value?.GetHashCode() ?? 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 遍历集合或数组集合
|
||||
foreach (var value in AsEnumerateArray())
|
||||
{
|
||||
// 递归计算元素的哈希码
|
||||
hash.Add(value?.GetHashCode() ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查两个单一对象实例是否相等
|
||||
/// </summary>
|
||||
/// <param name="clay1">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <param name="clay2">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
internal static bool AreObjectEqual(Clay clay1, Clay clay2) =>
|
||||
clay1.Count == clay2.Count && clay1.All((dynamic? item) =>
|
||||
clay2.HasProperty(item?.Key) && object.Equals(item?.Value, clay2[item?.Key]));
|
||||
|
||||
/// <summary>
|
||||
/// 检查两个集合或数组实例是否相等
|
||||
/// </summary>
|
||||
/// <param name="clay1">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <param name="clay2">
|
||||
/// <see cref="Clay" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="bool" />
|
||||
/// </returns>
|
||||
internal static bool AreArrayEqual(Clay clay1, Clay clay2)
|
||||
{
|
||||
// 检查集合或数组长度是否相等
|
||||
if (clay1.Count != clay2.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历检查每一项是否相等
|
||||
for (var i = 0; i < clay1.Count; i++)
|
||||
{
|
||||
if (!Equals(clay1[i], clay2[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -42,6 +42,10 @@ public partial class Clay
|
||||
return csharpInvokeMemberBinderType.CreatePropertyGetter(typeArgumentsProperty);
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>可用于控制序列化时能够被序列化的标识符。</remarks>
|
||||
public override IEnumerable<string> GetDynamicMemberNames() => Keys.Select(u => u.ToString()!);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object? result)
|
||||
{
|
||||
|
@@ -21,7 +21,7 @@ namespace ThingsGateway.Shapeless;
|
||||
/// <summary>
|
||||
/// 流变对象
|
||||
/// </summary>
|
||||
public partial class Clay : DynamicObject, IEnumerable<object?>, IFormattable
|
||||
public partial class Clay : DynamicObject, IEnumerable<object?>, IFormattable, IEquatable<Clay>
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="Clay" />
|
||||
@@ -535,6 +535,9 @@ public partial class Clay : DynamicObject, IEnumerable<object?>, IFormattable
|
||||
JsonNode jsonNode => jsonNode.DeepClone(),
|
||||
// 该操作不会复制自定义委托方法
|
||||
Clay clay => clay.DeepClone(options).JsonCanvas,
|
||||
// 排除 ExpandoObject 委托属性
|
||||
ExpandoObject expandoObject => SerializeToNode(
|
||||
expandoObject.Where(kvp => kvp.Value is not Delegate).ToDictionary(u => u.Key, u => u.Value), options),
|
||||
_ => JsonSerializer.SerializeToNode(obj, options?.JsonSerializerOptions)
|
||||
};
|
||||
|
||||
@@ -709,6 +712,60 @@ public partial class Clay : DynamicObject, IEnumerable<object?>, IFormattable
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行 <see cref="Clay" /> 实例的核心转换逻辑,支持严格模式和容错模式
|
||||
/// </summary>
|
||||
/// <param name="transformer">转换函数</param>
|
||||
/// <param name="strictMode">
|
||||
/// 模式开关:
|
||||
/// - true:严格模式(失败抛出异常)
|
||||
/// - false:容错模式(失败返回原对象)
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
internal Clay ExecuteTransformation(Func<dynamic, dynamic?> transformer, bool strictMode)
|
||||
{
|
||||
// 空检查
|
||||
ArgumentNullException.ThrowIfNull(transformer);
|
||||
|
||||
try
|
||||
{
|
||||
// 转换当前的流变对象
|
||||
var result = transformer(this);
|
||||
|
||||
// 检查转换结果是否是有效的流变对象
|
||||
if (result is not null && IsClay((object?)result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 严格模式下抛出异常
|
||||
if (strictMode)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Transformation must return a non-null Clay object. The provided function either returned null or an incompatible type.");
|
||||
}
|
||||
|
||||
// 非严格模式下降级返回原对象
|
||||
return this;
|
||||
}
|
||||
catch (Exception ex) when (strictMode)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"An unexpected error occurred during the transformation. Please verify the implementation of the transformation function.",
|
||||
ex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
// 非严格模式下降级返回原对象
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 抛出越界的数组索引异常
|
||||
/// </summary>
|
||||
|
@@ -0,0 +1,40 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ThingsGateway.Shapeless;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="object" /> 转 <see cref="Clay" /> JSON 序列化转换器
|
||||
/// </summary>
|
||||
public sealed class ObjectToClayJsonConverter : JsonConverter<object>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// 将 Utf8JsonReader 转换为 JsonElement
|
||||
var jsonElement = JsonElement.ParseValue(ref reader);
|
||||
|
||||
// 检查 JSON 是否是对象或数组类型
|
||||
if (jsonElement.ValueKind is JsonValueKind.Object or JsonValueKind.Array)
|
||||
{
|
||||
return Clay.Parse(jsonElement.ToString(), new ClayOptions { JsonSerializerOptions = options });
|
||||
}
|
||||
|
||||
return jsonElement;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) =>
|
||||
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
// ------------------------------------------------------------------------
|
||||
// 版权信息
|
||||
// 版权归百小僧及百签科技(广东)有限公司所有。
|
||||
// 所有权利保留。
|
||||
// 官方网站:https://baiqian.com
|
||||
//
|
||||
// 许可证信息
|
||||
// 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。
|
||||
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Shapeless.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 流变对象模块拓展类
|
||||
/// </summary>
|
||||
public static class ShapelessExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将对象转换为 <see cref="Clay" /> 实例
|
||||
/// </summary>
|
||||
/// <param name="obj">
|
||||
/// <see cref="object" />
|
||||
/// </param>
|
||||
/// <param name="options">
|
||||
/// <see cref="ClayOptions" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
public static Clay ToClay(this object? obj, ClayOptions? options = null) => Clay.Parse(obj, options);
|
||||
|
||||
/// <summary>
|
||||
/// 将对象转换为 <see cref="Clay" /> 实例
|
||||
/// </summary>
|
||||
/// <param name="obj">
|
||||
/// <see cref="object" />
|
||||
/// </param>
|
||||
/// <param name="configure">自定义配置委托</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
public static Clay ToClay(this object? obj, Action<ClayOptions> configure) => Clay.Parse(obj, configure);
|
||||
|
||||
/// <summary>
|
||||
/// 将 <see cref="Clay" /> 实例通过转换管道传递并返回新的 <see cref="Clay" />(失败时抛出异常)
|
||||
/// </summary>
|
||||
/// <param name="clayTask">
|
||||
/// <see cref="Task{TResult}" />
|
||||
/// </param>
|
||||
/// <param name="transformer">转换函数</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static async Task<Clay?> PipeAsync(this Task<Clay?> clayTask, Func<dynamic, dynamic?> transformer)
|
||||
{
|
||||
var clay = await clayTask.ConfigureAwait(false);
|
||||
return clay?.Pipe(transformer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将 <see cref="Clay" /> 实例通过转换管道传递,失败时返回原始对象
|
||||
/// </summary>
|
||||
/// <param name="clayTask">
|
||||
/// <see cref="Task{TResult}" />
|
||||
/// </param>
|
||||
/// <param name="transformer">returns</param>
|
||||
/// <returns>
|
||||
/// <see cref="Clay" />
|
||||
/// </returns>
|
||||
public static async Task<Clay?> PipeTryAsync(this Task<Clay?> clayTask, Func<dynamic, dynamic?> transformer)
|
||||
{
|
||||
var clay = await clayTask.ConfigureAwait(false);
|
||||
return clay?.PipeTry(transformer);
|
||||
}
|
||||
}
|
@@ -20,6 +20,17 @@ namespace Microsoft.Extensions.DependencyInjection;
|
||||
/// </summary>
|
||||
public static class ShapelessMvcBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加 <see cref="Clay" /> 配置
|
||||
/// </summary>
|
||||
/// <param name="builder">
|
||||
/// <see cref="IMvcBuilder" />
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see cref="IMvcBuilder" />
|
||||
/// </returns>
|
||||
public static IMvcBuilder AddClayOptions(this IMvcBuilder builder) => builder.AddClayOptions(_ => { });
|
||||
|
||||
/// <summary>
|
||||
/// 添加 <see cref="Clay" /> 配置
|
||||
/// </summary>
|
||||
|
@@ -12,7 +12,7 @@
|
||||
namespace ThingsGateway.Shapeless;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Clay" /> 对象事件数据
|
||||
/// <see cref="Clay" /> 对象事件参数
|
||||
/// </summary>
|
||||
public sealed class ClayEventArgs : EventArgs
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user