mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 15:43:59 +08:00 
			
		
		
		
	更新依赖
This commit is contained in:
		| @@ -1 +1 @@ | |||||||
| https://gitee.com/dotnetchina/Furion/commit/ef5310cbf2618584c1bfc812253309299ec620d7 | https://gitee.com/dotnetchina/Furion/commit/8bf85f6908c1630268e45eeec607267a03947d2b | ||||||
| @@ -486,20 +486,21 @@ public static class App | |||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     if (BakImagePaths.Contains(assemblyFileFullPath)) continue; |                     if (BakImagePaths.Contains(assemblyFileFullPath)) continue; | ||||||
|                     // 根据路径加载程序集 |                     //// 根据路径加载程序集 | ||||||
|                     //var loadedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFileFullPath); |                     ////var loadedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFileFullPath); | ||||||
|                     var runtimeAssembliesPath = Path.GetDirectoryName(typeof(object).Assembly.Location); |                     //var runtimeAssembliesPath = Path.GetDirectoryName(typeof(object).Assembly.Location); | ||||||
|                     // 将目标程序集和运行时核心程序集一起提供给 PathAssemblyResolver |                     //// 将目标程序集和运行时核心程序集一起提供给 PathAssemblyResolver | ||||||
|                     var assemblies = Directory.GetFiles(runtimeAssembliesPath, "*.dll"); |                     //var assemblies = Directory.GetFiles(runtimeAssembliesPath, "*.dll"); | ||||||
|                     var resolver = new PathAssemblyResolver(new[] { assemblyFileFullPath }.Concat(assemblies)); |                     //var resolver = new PathAssemblyResolver(new[] { assemblyFileFullPath }.Concat(assemblies)); | ||||||
|                     // 使用 MetadataLoadContext |                     //// 使用 MetadataLoadContext | ||||||
|                     using var metadataContext = new MetadataLoadContext(resolver); |                     //using var metadataContext = new MetadataLoadContext(resolver); | ||||||
|  |  | ||||||
|                     var referencedAssemblies = metadataContext.LoadFromAssemblyPath(assemblyFileFullPath)?.GetReferencedAssemblies(); |                     //var referencedAssemblies = metadataContext.LoadFromAssemblyPath(assemblyFileFullPath)?.GetReferencedAssemblies(); | ||||||
|                     if ((referencedAssemblies?.Any(a => a.Name.StartsWith("ThingsGateway"))) != true) |                     //if ((referencedAssemblies?.Any(a => a.Name.StartsWith("ThingsGateway"))) != true) | ||||||
|                     { |                     //{ | ||||||
|                         continue; |                     //    continue; | ||||||
|                     } |                     //} | ||||||
|  |                      | ||||||
|                     var loadedAssembly = Reflect.LoadAssembly(assemblyFileFullPath); |                     var loadedAssembly = Reflect.LoadAssembly(assemblyFileFullPath); | ||||||
|                     if (loadedAssembly == default) continue; |                     if (loadedAssembly == default) continue; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -177,7 +177,10 @@ public static class AppServiceCollectionExtensions | |||||||
|     public static IServiceCollection AddAppHostedService(this IServiceCollection services) |     public static IServiceCollection AddAppHostedService(this IServiceCollection services) | ||||||
|     { |     { | ||||||
|         // 获取所有 BackgroundService 类型,排除泛型主机 |         // 获取所有 BackgroundService 类型,排除泛型主机 | ||||||
|         var backgroundServiceTypes = App.EffectiveTypes.Where(u => !u.IsAbstract && !u.IsInterface && typeof(IHostedService).IsAssignableFrom(u) && u.Name != "GenericWebHostService"); |         var backgroundServiceTypes = App.EffectiveTypes.Where(u => !u.IsAbstract && !u.IsInterface && !u.IsGenericType | ||||||
|  |                     && typeof(IHostedService).IsAssignableFrom(u) && u.Name != "GenericWebHostService" | ||||||
|  |                     && !services.Any(c => c.ServiceType == typeof(IHostedService) && c.ImplementationType != u)); | ||||||
|  |  | ||||||
|         var addHostServiceMethod = typeof(ServiceCollectionHostedServiceExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public) |         var addHostServiceMethod = typeof(ServiceCollectionHostedServiceExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public) | ||||||
|                             .Where(u => u.Name.Equals("AddHostedService") && u.IsGenericMethod && u.GetParameters().Length == 1) |                             .Where(u => u.Name.Equals("AddHostedService") && u.IsGenericMethod && u.GetParameters().Length == 1) | ||||||
|                             .FirstOrDefault(); |                             .FirstOrDefault(); | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ namespace ThingsGateway.AspNetCore; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// 数组 URL 地址参数模型绑定特性 | /// 数组 URL 地址参数模型绑定特性 | ||||||
| /// </summary> | /// </summary> | ||||||
| [SuppressSniffer] | [SuppressSniffer, AttributeUsage(AttributeTargets.Parameter)] | ||||||
| public sealed class FlexibleArrayAttribute<T> : ModelBinderAttribute | public sealed class FlexibleArrayAttribute<T> : ModelBinderAttribute | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -170,7 +170,7 @@ public sealed class ScheduleOptionsBuilder | |||||||
|     public ScheduleOptionsBuilder AddJob(params SchedulerBuilder[] schedulerBuilders) |     public ScheduleOptionsBuilder AddJob(params SchedulerBuilder[] schedulerBuilders) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         if (schedulerBuilders == null || schedulerBuilders.Length == 0) throw new ArgumentNullException(nameof(schedulerBuilders)); |         if (schedulerBuilders == null) throw new ArgumentNullException(nameof(schedulerBuilders)); | ||||||
|  |  | ||||||
|         // 逐条将作业计划构建器添加到集合中 |         // 逐条将作业计划构建器添加到集合中 | ||||||
|         foreach (var schedulerBuilder in schedulerBuilders) |         foreach (var schedulerBuilder in schedulerBuilders) | ||||||
|   | |||||||
| @@ -93,8 +93,11 @@ internal sealed class JobCancellationToken : IJobCancellationToken | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             catch (TaskCanceledException) { } |             catch (OperationCanceledException) { } | ||||||
|             catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException) { } |             catch (Exception ex) when (!(ex is OperationCanceledException || | ||||||
|  |                 ex is ObjectDisposedException || | ||||||
|  |                 (ex is AggregateException aggEx && aggEx.InnerExceptions.All(e => e is OperationCanceledException || e is ObjectDisposedException)))) | ||||||
|  |             { } | ||||||
|             catch { } |             catch { } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ public abstract class JobExecutionContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 触发模式 |     /// 触发模式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> |     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> | ||||||
|     public int Mode { get; internal set; } |     public int Mode { get; internal set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -41,6 +41,6 @@ public sealed class JobFactoryContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 触发模式 |     /// 触发模式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> |     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> | ||||||
|     public int Mode { get; internal set; } |     public int Mode { get; internal set; } | ||||||
| } | } | ||||||
| @@ -61,7 +61,7 @@ public sealed class PersistenceExecutionRecordContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 触发模式 |     /// 触发模式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> |     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> | ||||||
|     public int Mode { get; } |     public int Mode { get; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ public sealed class PersistenceTriggerContext : PersistenceContext | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 触发模式 |     /// 触发模式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> |     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> | ||||||
|     public int Mode { get; internal set; } |     public int Mode { get; internal set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -334,7 +334,7 @@ internal sealed partial class SchedulerFactory | |||||||
|     public void SaveJob(params SchedulerBuilder[] schedulerBuilders) |     public void SaveJob(params SchedulerBuilder[] schedulerBuilders) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         if (schedulerBuilders == null || schedulerBuilders.Length == 0) throw new ArgumentNullException(nameof(schedulerBuilders)); |         if (schedulerBuilders == null) throw new ArgumentNullException(nameof(schedulerBuilders)); | ||||||
|  |  | ||||||
|         // 逐条将作业计划构建器保存到作业计划中 |         // 逐条将作业计划构建器保存到作业计划中 | ||||||
|         foreach (var schedulerBuilder in schedulerBuilders) |         foreach (var schedulerBuilder in schedulerBuilders) | ||||||
| @@ -387,7 +387,7 @@ internal sealed partial class SchedulerFactory | |||||||
|     public void AddJob(params SchedulerBuilder[] schedulerBuilders) |     public void AddJob(params SchedulerBuilder[] schedulerBuilders) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         if (schedulerBuilders == null || schedulerBuilders.Length == 0) throw new ArgumentNullException(nameof(schedulerBuilders)); |         if (schedulerBuilders == null) throw new ArgumentNullException(nameof(schedulerBuilders)); | ||||||
|  |  | ||||||
|         // 逐条将作业计划构建器保存到作业计划中 |         // 逐条将作业计划构建器保存到作业计划中 | ||||||
|         foreach (var schedulerBuilder in schedulerBuilders) |         foreach (var schedulerBuilder in schedulerBuilders) | ||||||
| @@ -1026,7 +1026,7 @@ internal sealed partial class SchedulerFactory | |||||||
|     public void UpdateJob(params SchedulerBuilder[] schedulerBuilders) |     public void UpdateJob(params SchedulerBuilder[] schedulerBuilders) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         if (schedulerBuilders == null || schedulerBuilders.Length == 0) throw new ArgumentNullException(nameof(schedulerBuilders)); |         if (schedulerBuilders == null) throw new ArgumentNullException(nameof(schedulerBuilders)); | ||||||
|  |  | ||||||
|         // 逐条将作业计划构建器保存到作业计划中 |         // 逐条将作业计划构建器保存到作业计划中 | ||||||
|         foreach (var schedulerBuilder in schedulerBuilders) |         foreach (var schedulerBuilder in schedulerBuilders) | ||||||
|   | |||||||
| @@ -20,6 +20,11 @@ namespace ThingsGateway.Schedule; | |||||||
| /// </summary> | /// </summary> | ||||||
| internal sealed partial class SchedulerFactory : ISchedulerFactory | internal sealed partial class SchedulerFactory : ISchedulerFactory | ||||||
| { | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// 取消作业调度器休眠状态并发锁 | ||||||
|  |     /// </summary> | ||||||
|  |     private readonly object _lock = new(); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 作业计划变更通知 |     /// 作业计划变更通知 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -297,21 +302,25 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public void CancelSleep() |     public void CancelSleep() | ||||||
|     { |     { | ||||||
|         try |         lock (_lock) | ||||||
|         { |         { | ||||||
|             // 取消休眠,如果存在错误立即抛出 |             try | ||||||
|             _sleepCancellationTokenSource.Cancel(true); |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             // 输出非任务取消异常日志 |  | ||||||
|             if (!(ex is TaskCanceledException || (ex is AggregateException aggEx && aggEx.InnerExceptions.Count == 1 && aggEx.InnerExceptions[0] is TaskCanceledException))) |  | ||||||
|             { |             { | ||||||
|                 _logger.LogError(ex, ex.Message); |                 // 取消休眠,如果存在错误立即抛出 | ||||||
|  |                 _sleepCancellationTokenSource.Cancel(true); | ||||||
|  |             } | ||||||
|  |             // 非任务取消异常日志 | ||||||
|  |             catch (Exception ex) when (!(ex is OperationCanceledException || | ||||||
|  |                 ex is ObjectDisposedException || | ||||||
|  |                 (ex is AggregateException aggEx && aggEx.InnerExceptions.All(e => e is OperationCanceledException || e is ObjectDisposedException)))) | ||||||
|  |             { | ||||||
|  |                 _logger.LogError(ex, $"Error canceling sleep. {ex.Message}"); | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 // 重新初始化作业调度器取消休眠 Token | ||||||
|  |                 CreateCancellationTokenSource(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // 重新初始化作业调度器取消休眠 Token |  | ||||||
|             CreateCancellationTokenSource(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -380,7 +380,7 @@ internal sealed class ScheduleHostedService : BackgroundService | |||||||
|                             // 记录作业触发器运行信息 |                             // 记录作业触发器运行信息 | ||||||
|                             await trigger.RecordTimelineAsync(_schedulerFactory, jobId, executionException?.ToString()).ConfigureAwait(false); |                             await trigger.RecordTimelineAsync(_schedulerFactory, jobId, executionException?.ToString()).ConfigureAwait(false); | ||||||
|  |  | ||||||
|                             // 重置触发模式:0:定时,1:手动 |                             // 重置触发模式:0:定时,1:手动 | ||||||
|                             trigger.Mode = 0; |                             trigger.Mode = 0; | ||||||
|  |  | ||||||
|                             // 处理临时作业,执行完成后移除 |                             // 处理临时作业,执行完成后移除 | ||||||
|   | |||||||
| @@ -194,6 +194,6 @@ public partial class Trigger | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 触发模式 |     /// 触发模式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> |     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> | ||||||
|     internal int Mode { get; set; } |     internal int Mode { get; set; } | ||||||
| } | } | ||||||
| @@ -76,7 +76,7 @@ public sealed class TriggerTimeline : IDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 触发模式 |     /// 触发模式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> |     /// <remarks>默认为定时触发,0:定时,1:手动</remarks> | ||||||
|     [JsonInclude] |     [JsonInclude] | ||||||
|     public int Mode { get; internal set; } |     public int Mode { get; internal set; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Reflection; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     public class EnhancedStackFrame : StackFrame | ||||||
|  |     { | ||||||
|  |         private readonly string? _fileName; | ||||||
|  |         private readonly int _lineNumber; | ||||||
|  |         private readonly int _colNumber; | ||||||
|  |  | ||||||
|  |         public StackFrame StackFrame { get; } | ||||||
|  |  | ||||||
|  |         public bool IsRecursive  | ||||||
|  |         {  | ||||||
|  |             get => MethodInfo.RecurseCount > 0; | ||||||
|  |             internal set => MethodInfo.RecurseCount++; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public ResolvedMethod MethodInfo { get; } | ||||||
|  |  | ||||||
|  |         internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) | ||||||
|  |             : base(fileName, lineNumber, colNumber) | ||||||
|  |         { | ||||||
|  |             StackFrame = stackFrame; | ||||||
|  |             MethodInfo = methodInfo; | ||||||
|  |  | ||||||
|  |             _fileName = fileName; | ||||||
|  |             _lineNumber = lineNumber; | ||||||
|  |             _colNumber = colNumber; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) | ||||||
|  |         { | ||||||
|  |             return _lineNumber == lineNumber && | ||||||
|  |                 _colNumber == colNumber && | ||||||
|  |                 _fileName == fileName && | ||||||
|  |                 MethodInfo.IsSequentialEquivalent(methodInfo); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///      Gets the column number in the file that contains the code that is executing.  | ||||||
|  |         ///      This information is typically extracted from the debugging symbols for the executable. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The file column number, or 0 (zero) if the file column number cannot be determined.</returns> | ||||||
|  |         public override int GetFileColumnNumber() => _colNumber; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///     Gets the line number in the file that contains the code that is executing.  | ||||||
|  |         ///     This information is typically extracted from the debugging symbols for the executable. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The file line number, or 0 (zero) if the file line number cannot be determined.</returns> | ||||||
|  |         public override int GetFileLineNumber() => _lineNumber; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///     Gets the file name that contains the code that is executing.  | ||||||
|  |         ///     This information is typically extracted from the debugging symbols for the executable. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The file name, or null if the file name cannot be determined.</returns> | ||||||
|  |         public override string? GetFileName() => _fileName; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///    Gets the offset from the start of the Microsoft intermediate language (MSIL) | ||||||
|  |         ///    code for the method that is executing. This offset might be an approximation | ||||||
|  |         ///    depending on whether or not the just-in-time (JIT) compiler is generating debugging | ||||||
|  |         ///    code. The generation of this debugging information is controlled by the System.Diagnostics.DebuggableAttribute. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The offset from the start of the MSIL code for the method that is executing.</returns> | ||||||
|  |         public override int GetILOffset() => StackFrame.GetILOffset(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///     Gets the method in which the frame is executing. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The method in which the frame is executing.</returns> | ||||||
|  |         public override MethodBase? GetMethod() => StackFrame.GetMethod(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///     Gets the offset from the start of the native just-in-time (JIT)-compiled code | ||||||
|  |         ///     for the method that is being executed. The generation of this debugging information | ||||||
|  |         ///     is controlled by the System.Diagnostics.DebuggableAttribute class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The offset from the start of the JIT-compiled code for the method that is being executed.</returns> | ||||||
|  |         public override int GetNativeOffset() => StackFrame.GetNativeOffset(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///     Builds a readable representation of the stack trace. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>A readable representation of the stack trace.</returns> | ||||||
|  |         public override string ToString() => MethodInfo.ToString(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,944 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  | // Copyright (c) .NET Foundation. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Generic.Enumerable; | ||||||
|  | using System.Diagnostics.Internal; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Runtime.ExceptionServices; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     public partial class EnhancedStackTrace | ||||||
|  |     { | ||||||
|  |         private static readonly Type? StackTraceHiddenAttributeType = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false); | ||||||
|  |         private static readonly Type? AsyncIteratorStateMachineAttributeType = Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); | ||||||
|  |  | ||||||
|  |         static EnhancedStackTrace() | ||||||
|  |         { | ||||||
|  |             if (AsyncIteratorStateMachineAttributeType != null) return; | ||||||
|  |  | ||||||
|  |             Assembly mba; | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 mba = Assembly.Load("Microsoft.Bcl.AsyncInterfaces"); | ||||||
|  |             } | ||||||
|  |             catch | ||||||
|  |             { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             AsyncIteratorStateMachineAttributeType = mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static List<EnhancedStackFrame> GetFrames(Exception exception) | ||||||
|  |         { | ||||||
|  |             if (exception == null) | ||||||
|  |             { | ||||||
|  |                 return new List<EnhancedStackFrame>(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var needFileInfo = true; | ||||||
|  |             var stackTrace = new StackTrace(exception, needFileInfo); | ||||||
|  |  | ||||||
|  |             return GetFrames(stackTrace); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static List<EnhancedStackFrame> GetFrames(StackTrace stackTrace) | ||||||
|  |         { | ||||||
|  |             var frames = new List<EnhancedStackFrame>(); | ||||||
|  |             var stackFrames = stackTrace.GetFrames(); | ||||||
|  |  | ||||||
|  |             if (stackFrames == null) | ||||||
|  |             { | ||||||
|  |                 return frames; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             EnhancedStackFrame? lastFrame = null; | ||||||
|  |             PortablePdbReader? portablePdbReader = null; | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 for (var i = 0; i < stackFrames.Length; i++) | ||||||
|  |                 { | ||||||
|  |                     var frame = stackFrames[i]; | ||||||
|  |                     if (frame is null) | ||||||
|  |                     { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     var method = frame.GetMethod(); | ||||||
|  |  | ||||||
|  |                     // Always show last stackFrame | ||||||
|  |                     if (method != null && !ShowInStackTrace(method) && i < stackFrames.Length - 1) | ||||||
|  |                     { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var fileName = frame.GetFileName(); | ||||||
|  |                     var row = frame.GetFileLineNumber(); | ||||||
|  |                     var column = frame.GetFileColumnNumber(); | ||||||
|  |                     var ilOffset = frame.GetILOffset(); | ||||||
|  |                     if (method != null && string.IsNullOrEmpty(fileName) && ilOffset >= 0) | ||||||
|  |                     { | ||||||
|  |                         // .NET Framework and older versions of mono don't support portable PDBs | ||||||
|  |                         // so we read it manually to get file name and line information | ||||||
|  |                         (portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (method is null) | ||||||
|  |                     { | ||||||
|  |                         // Method can't be null | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var resolvedMethod = GetMethodDisplayString(method); | ||||||
|  |                     if (lastFrame?.IsEquivalent(resolvedMethod, fileName, row, column) ?? false) | ||||||
|  |                     { | ||||||
|  |                         lastFrame.IsRecursive = true; | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column); | ||||||
|  |                         frames.Add(stackFrame); | ||||||
|  |                         lastFrame = stackFrame; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 portablePdbReader?.Dispose(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return frames; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod) | ||||||
|  |         { | ||||||
|  |             var method = originMethod; | ||||||
|  |  | ||||||
|  |             var methodDisplayInfo = new ResolvedMethod | ||||||
|  |             { | ||||||
|  |                 SubMethodBase = method | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             // Type name | ||||||
|  |             var type = method.DeclaringType; | ||||||
|  |  | ||||||
|  |             var subMethodName = method.Name; | ||||||
|  |             var methodName = method.Name; | ||||||
|  |  | ||||||
|  |             var isAsyncStateMachine = typeof(IAsyncStateMachine).IsAssignableFrom(type); | ||||||
|  |             if (isAsyncStateMachine || typeof(IEnumerator).IsAssignableFrom(type)) | ||||||
|  |             { | ||||||
|  |                 methodDisplayInfo.IsAsync = isAsyncStateMachine; | ||||||
|  |  | ||||||
|  |                 // Convert StateMachine methods to correct overload +MoveNext() | ||||||
|  |                 if (!TryResolveStateMachineMethod(ref method, out type)) | ||||||
|  |                 { | ||||||
|  |                     methodDisplayInfo.SubMethodBase = null; | ||||||
|  |                     subMethodName = null; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 methodName = method.Name; | ||||||
|  |             } | ||||||
|  |             else if (IsFSharpAsync(method)) | ||||||
|  |             { | ||||||
|  |                 methodDisplayInfo.IsAsync = true; | ||||||
|  |                 methodDisplayInfo.SubMethodBase = null; | ||||||
|  |                 subMethodName = null; | ||||||
|  |                 methodName = null; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Method name | ||||||
|  |             methodDisplayInfo.MethodBase = method; | ||||||
|  |             methodDisplayInfo.Name = methodName; | ||||||
|  |             if (method.Name.IndexOf('<') >= 0) | ||||||
|  |             { | ||||||
|  |                 if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal)) | ||||||
|  |                 { | ||||||
|  |                     methodName = method.Name; | ||||||
|  |                     methodDisplayInfo.MethodBase = method; | ||||||
|  |                     methodDisplayInfo.Name = methodName; | ||||||
|  |                     methodDisplayInfo.Ordinal = ordinal; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     methodDisplayInfo.MethodBase = null; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod); | ||||||
|  |  | ||||||
|  |                 if (methodDisplayInfo.IsLambda && type != null) | ||||||
|  |                 { | ||||||
|  |                     if (methodName == ".cctor") | ||||||
|  |                     { | ||||||
|  |                         if (type.IsGenericTypeDefinition && !type.IsConstructedGenericType) | ||||||
|  |                         { | ||||||
|  |                             // TODO: diagnose type's generic type arguments from frame's "this" or something | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); | ||||||
|  |                             foreach (var field in fields) | ||||||
|  |                             { | ||||||
|  |                                 var value = field.GetValue(field); | ||||||
|  |                                 if (value is Delegate d && d.Target is not null) | ||||||
|  |                                 { | ||||||
|  |                                     if (ReferenceEquals(d.Method, originMethod) && | ||||||
|  |                                         d.Target.ToString() == originMethod.DeclaringType?.ToString()) | ||||||
|  |                                     { | ||||||
|  |                                         methodDisplayInfo.Name = field.Name; | ||||||
|  |                                         methodDisplayInfo.IsLambda = false; | ||||||
|  |                                         method = originMethod; | ||||||
|  |                                         break; | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (subMethodName != methodName) | ||||||
|  |             { | ||||||
|  |                 methodDisplayInfo.SubMethod = subMethodName; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // ResolveStateMachineMethod may have set declaringType to null | ||||||
|  |             if (type != null) | ||||||
|  |             { | ||||||
|  |                 methodDisplayInfo.DeclaringType = type; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (method is MethodInfo mi) | ||||||
|  |             { | ||||||
|  |                 var returnParameter = mi.ReturnParameter; | ||||||
|  |                 if (returnParameter != null) | ||||||
|  |                 { | ||||||
|  |                     methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter); | ||||||
|  |                 } | ||||||
|  |                 else if (mi.ReturnType != null) | ||||||
|  |                 { | ||||||
|  |                     methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType) | ||||||
|  |                     { | ||||||
|  |                         Prefix = "", | ||||||
|  |                         Name = "", | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (method.IsGenericMethod) | ||||||
|  |             { | ||||||
|  |                 var genericArguments = method.GetGenericArguments(); | ||||||
|  |                 var genericArgumentsString = string.Join(", ", genericArguments | ||||||
|  |                     .Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); | ||||||
|  |                 methodDisplayInfo.GenericArguments += "<" + genericArgumentsString + ">"; | ||||||
|  |                 methodDisplayInfo.ResolvedGenericArguments = genericArguments; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Method parameters | ||||||
|  |             var parameters = method.GetParameters(); | ||||||
|  |             if (parameters.Length > 0) | ||||||
|  |             { | ||||||
|  |                 var parameterList = new List<ResolvedParameter>(parameters.Length); | ||||||
|  |                 foreach (var parameter in parameters) | ||||||
|  |                 { | ||||||
|  |                     parameterList.Add(GetParameter(parameter)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 methodDisplayInfo.Parameters = parameterList; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (methodDisplayInfo.SubMethodBase == methodDisplayInfo.MethodBase) | ||||||
|  |             { | ||||||
|  |                 methodDisplayInfo.SubMethodBase = null; | ||||||
|  |             } | ||||||
|  |             else if (methodDisplayInfo.SubMethodBase != null) | ||||||
|  |             { | ||||||
|  |                 parameters = methodDisplayInfo.SubMethodBase.GetParameters(); | ||||||
|  |                 if (parameters.Length > 0) | ||||||
|  |                 { | ||||||
|  |                     var parameterList = new List<ResolvedParameter>(parameters.Length); | ||||||
|  |                     foreach (var parameter in parameters) | ||||||
|  |                     { | ||||||
|  |                         var param = GetParameter(parameter); | ||||||
|  |                         if (param.Name?.StartsWith('<') ?? true) continue; | ||||||
|  |  | ||||||
|  |                         parameterList.Add(param); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     methodDisplayInfo.SubMethodParameters = parameterList; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return methodDisplayInfo; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static bool IsFSharpAsync(MethodBase method) | ||||||
|  |         { | ||||||
|  |             if (method is MethodInfo minfo) | ||||||
|  |             { | ||||||
|  |                 var returnType = minfo.ReturnType; | ||||||
|  |                 if (returnType.Namespace == "Microsoft.FSharp.Control" && returnType.Name == "FSharpAsync`1") | ||||||
|  |                 { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static bool TryResolveGeneratedName(ref MethodBase method, out Type? type, out string methodName, out string? subMethodName, out GeneratedNameKind kind, out int? ordinal) | ||||||
|  |         { | ||||||
|  |             kind = GeneratedNameKind.None; | ||||||
|  |             type = method.DeclaringType; | ||||||
|  |             subMethodName = null; | ||||||
|  |             ordinal = null; | ||||||
|  |             methodName = method.Name; | ||||||
|  |  | ||||||
|  |             var generatedName = methodName; | ||||||
|  |  | ||||||
|  |             if (!TryParseGeneratedName(generatedName, out kind, out var openBracketOffset, out var closeBracketOffset)) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             methodName = generatedName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); | ||||||
|  |  | ||||||
|  |             switch (kind) | ||||||
|  |             { | ||||||
|  |                 case GeneratedNameKind.LocalFunction: | ||||||
|  |                     { | ||||||
|  |                         var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1); | ||||||
|  |                         if (localNameStart < 0) break; | ||||||
|  |                         localNameStart += 3; | ||||||
|  |  | ||||||
|  |                         if (localNameStart < generatedName.Length) | ||||||
|  |                         { | ||||||
|  |                             var localNameEnd = generatedName.IndexOf('|', localNameStart); | ||||||
|  |                             if (localNameEnd > 0) | ||||||
|  |                             { | ||||||
|  |                                 subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case GeneratedNameKind.LambdaMethod: | ||||||
|  |                     subMethodName = ""; | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var dt = method.DeclaringType; | ||||||
|  |             if (dt == null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var matchHint = GetMatchHint(kind, method); | ||||||
|  |  | ||||||
|  |             var matchName = methodName; | ||||||
|  |  | ||||||
|  |             var candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); | ||||||
|  |             if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true; | ||||||
|  |  | ||||||
|  |             var candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); | ||||||
|  |             if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; | ||||||
|  |  | ||||||
|  |             const int MaxResolveDepth = 10; | ||||||
|  |             for (var i = 0; i < MaxResolveDepth; i++) | ||||||
|  |             { | ||||||
|  |                 dt = dt.DeclaringType; | ||||||
|  |                 if (dt == null) | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); | ||||||
|  |                 if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true; | ||||||
|  |  | ||||||
|  |                 candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); | ||||||
|  |                 if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; | ||||||
|  |  | ||||||
|  |                 if (methodName == ".cctor") | ||||||
|  |                 { | ||||||
|  |                     candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); | ||||||
|  |                     foreach (var cctor in candidateConstructors) | ||||||
|  |                     { | ||||||
|  |                         method = cctor; | ||||||
|  |                         type = dt; | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static bool TryResolveSourceMethod(IEnumerable<MethodBase> candidateMethods, GeneratedNameKind kind, string? matchHint, ref MethodBase method, ref Type? type, out int? ordinal) | ||||||
|  |         { | ||||||
|  |             ordinal = null; | ||||||
|  |             foreach (var candidateMethod in candidateMethods) | ||||||
|  |             { | ||||||
|  |                 if (candidateMethod.GetMethodBody() is not { } methodBody) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 if (kind == GeneratedNameKind.LambdaMethod) | ||||||
|  |                 { | ||||||
|  |                     foreach (var v in EnumerableIList.Create(methodBody.LocalVariables)) | ||||||
|  |                     { | ||||||
|  |                         if (v.LocalType == type) | ||||||
|  |                         { | ||||||
|  |                             GetOrdinal(method, ref ordinal); | ||||||
|  |  | ||||||
|  |                         } | ||||||
|  |                         method = candidateMethod; | ||||||
|  |                         type = method.DeclaringType; | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     var rawIl = methodBody.GetILAsByteArray(); | ||||||
|  |                     if (rawIl is null) | ||||||
|  |                     { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     var reader = new ILReader(rawIl); | ||||||
|  |                     while (reader.Read(candidateMethod)) | ||||||
|  |                     { | ||||||
|  |                         if (reader.Operand is MethodBase mb) | ||||||
|  |                         { | ||||||
|  |                             if (method == mb || matchHint != null && method.Name.Contains(matchHint)) | ||||||
|  |                             { | ||||||
|  |                                 if (kind == GeneratedNameKind.LambdaMethod) | ||||||
|  |                                 { | ||||||
|  |                                     GetOrdinal(method, ref ordinal); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 method = candidateMethod; | ||||||
|  |                                 type = method.DeclaringType; | ||||||
|  |                                 return true; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 catch | ||||||
|  |                 { | ||||||
|  |                     // https://github.com/benaadams/Ben.Demystifier/issues/32 | ||||||
|  |                     // Skip methods where il can't be interpreted | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void GetOrdinal(MethodBase method, ref int? ordinal) | ||||||
|  |         { | ||||||
|  |             var lamdaStart = method.Name.IndexOf((char)GeneratedNameKind.LambdaMethod + "__") + 3; | ||||||
|  |             if (lamdaStart > 3) | ||||||
|  |             { | ||||||
|  |                 var secondStart = method.Name.IndexOf('_', lamdaStart) + 1; | ||||||
|  |                 if (secondStart > 0) | ||||||
|  |                 { | ||||||
|  |                     lamdaStart = secondStart; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!int.TryParse(method.Name.AsSpan(lamdaStart), out var foundOrdinal)) | ||||||
|  |                 { | ||||||
|  |                     ordinal = null; | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 ordinal = foundOrdinal; | ||||||
|  |  | ||||||
|  |                 var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); | ||||||
|  |  | ||||||
|  |                 var count = 0; | ||||||
|  |                 if (methods != null) | ||||||
|  |                 { | ||||||
|  |                     var startName = method.Name.Substring(0, lamdaStart); | ||||||
|  |                     foreach (var m in methods) | ||||||
|  |                     { | ||||||
|  |                         if (m.Name.Length > lamdaStart && m.Name.StartsWith(startName)) | ||||||
|  |                         { | ||||||
|  |                             count++; | ||||||
|  |  | ||||||
|  |                             if (count > 1) | ||||||
|  |                             { | ||||||
|  |                                 break; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (count <= 1) | ||||||
|  |                 { | ||||||
|  |                     ordinal = null; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         static string? GetMatchHint(GeneratedNameKind kind, MethodBase method) | ||||||
|  |         { | ||||||
|  |             var methodName = method.Name; | ||||||
|  |  | ||||||
|  |             switch (kind) | ||||||
|  |             { | ||||||
|  |                 case GeneratedNameKind.LocalFunction: | ||||||
|  |                     var start = methodName.IndexOf('|'); | ||||||
|  |                     if (start < 1) return null; | ||||||
|  |                     var end = methodName.IndexOf('_', start) + 1; | ||||||
|  |                     if (end <= start) return null; | ||||||
|  |  | ||||||
|  |                     return methodName.Substring(start, end - start); | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Parse the generated name. Returns true for names of the form | ||||||
|  |         // [CS$]<[middle]>c[__[suffix]] where [CS$] is included for certain | ||||||
|  |         // generated names, where [middle] and [__[suffix]] are optional, | ||||||
|  |         // and where c is a single character in [1-9a-z] | ||||||
|  |         // (csharp\LanguageAnalysis\LIB\SpecialName.cpp). | ||||||
|  |         internal static bool TryParseGeneratedName( | ||||||
|  |             string name, | ||||||
|  |             out GeneratedNameKind kind, | ||||||
|  |             out int openBracketOffset, | ||||||
|  |             out int closeBracketOffset) | ||||||
|  |         { | ||||||
|  |             openBracketOffset = -1; | ||||||
|  |             if (name.StartsWith("CS$<", StringComparison.Ordinal)) | ||||||
|  |             { | ||||||
|  |                 openBracketOffset = 3; | ||||||
|  |             } | ||||||
|  |             else if (name.StartsWith('<')) | ||||||
|  |             { | ||||||
|  |                 openBracketOffset = 0; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (openBracketOffset >= 0) | ||||||
|  |             { | ||||||
|  |                 closeBracketOffset = IndexOfBalancedParenthesis(name, openBracketOffset, '>'); | ||||||
|  |                 if (closeBracketOffset >= 0 && closeBracketOffset + 1 < name.Length) | ||||||
|  |                 { | ||||||
|  |                     int c = name[closeBracketOffset + 1]; | ||||||
|  |                     if ((c >= '1' && c <= '9') || (c >= 'a' && c <= 'z')) // Note '0' is not special. | ||||||
|  |                     { | ||||||
|  |                         kind = (GeneratedNameKind)c; | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             kind = GeneratedNameKind.None; | ||||||
|  |             openBracketOffset = -1; | ||||||
|  |             closeBracketOffset = -1; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing) | ||||||
|  |         { | ||||||
|  |             var opening = str[openingOffset]; | ||||||
|  |  | ||||||
|  |             var depth = 1; | ||||||
|  |             for (var i = openingOffset + 1; i < str.Length; i++) | ||||||
|  |             { | ||||||
|  |                 var c = str[i]; | ||||||
|  |                 if (c == opening) | ||||||
|  |                 { | ||||||
|  |                     depth++; | ||||||
|  |                 } | ||||||
|  |                 else if (c == closing) | ||||||
|  |                 { | ||||||
|  |                     depth--; | ||||||
|  |                     if (depth == 0) | ||||||
|  |                     { | ||||||
|  |                         return i; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static string GetPrefix(ParameterInfo parameter) | ||||||
|  |         { | ||||||
|  |             if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false)) | ||||||
|  |             { | ||||||
|  |                 return "params"; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (parameter.IsOut) | ||||||
|  |             { | ||||||
|  |                 return "out"; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (parameter.IsIn) | ||||||
|  |             { | ||||||
|  |                 return "in"; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (parameter.ParameterType.IsByRef) | ||||||
|  |             { | ||||||
|  |                 return "ref"; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return string.Empty; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static ResolvedParameter GetParameter(ParameterInfo parameter) | ||||||
|  |         { | ||||||
|  |             var prefix = GetPrefix(parameter); | ||||||
|  |             var parameterType = parameter.ParameterType; | ||||||
|  |  | ||||||
|  |             if (parameterType.IsGenericType) | ||||||
|  |             { | ||||||
|  |                 var customAttribs = parameter.GetCustomAttributes(inherit: false); | ||||||
|  |  | ||||||
|  |                 var tupleNameAttribute = customAttribs.OfType<Attribute>().FirstOrDefault(a => a.IsTupleElementNameAttribute()); | ||||||
|  |  | ||||||
|  |                 var tupleNames = tupleNameAttribute?.GetTransformerNames(); | ||||||
|  |  | ||||||
|  |                 if (tupleNames?.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (parameterType.IsByRef && parameterType.GetElementType() is {} elementType) | ||||||
|  |             { | ||||||
|  |                 parameterType = elementType; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new ResolvedParameter(parameterType) | ||||||
|  |             { | ||||||
|  |                 Prefix = prefix, | ||||||
|  |                 Name = parameter.Name, | ||||||
|  |                 IsDynamicType = parameter.IsDefined(typeof(DynamicAttribute), false) | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static ValueTupleResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string? name, Type parameterType) | ||||||
|  |         { | ||||||
|  |             return new ValueTupleResolvedParameter(parameterType, tupleNames) | ||||||
|  |             { | ||||||
|  |                 Prefix = prefix, | ||||||
|  |                 Name = name, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static string GetValueTupleParameterName(IList<string> tupleNames, Type parameterType) | ||||||
|  |         { | ||||||
|  |             var sb = new StringBuilder(); | ||||||
|  |             sb.Append('('); | ||||||
|  |             var args = parameterType.GetGenericArguments(); | ||||||
|  |             for (var i = 0; i < args.Length; i++) | ||||||
|  |             { | ||||||
|  |                 if (i > 0) | ||||||
|  |                 { | ||||||
|  |                     sb.Append(", "); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 sb.Append(TypeNameHelper.GetTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true)); | ||||||
|  |  | ||||||
|  |                 if (i >= tupleNames.Count) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var argName = tupleNames[i]; | ||||||
|  |                 if (argName == null) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 sb.Append(' '); | ||||||
|  |                 sb.Append(argName); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             sb.Append(')'); | ||||||
|  |             return sb.ToString(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static bool ShowInStackTrace(MethodBase method) | ||||||
|  |         { | ||||||
|  |             // Since .NET 5: | ||||||
|  |             // https://github.com/dotnet/runtime/blob/7c18d4d6488dab82124d475d1199def01d1d252c/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L348-L361 | ||||||
|  |             if ((method.MethodImplementationFlags & MethodImplAttributes.AggressiveInlining) != 0) | ||||||
|  |             { | ||||||
|  |                 // Aggressive Inlines won't normally show in the StackTrace; however for Tier0 Jit and | ||||||
|  |                 // cross-assembly AoT/R2R these inlines will be blocked until Tier1 Jit re-Jits | ||||||
|  |                 // them when they will inline. We don't show them in the StackTrace to bring consistency | ||||||
|  |                 // between this first-pass asm and fully optimized asm. | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Since .NET Core 2: | ||||||
|  |             if (StackTraceHiddenAttributeType != null) | ||||||
|  |             { | ||||||
|  |                 // Don't show any methods marked with the StackTraceHiddenAttribute | ||||||
|  |                 // https://github.com/dotnet/coreclr/pull/14652 | ||||||
|  |                 if (IsStackTraceHidden(method)) | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var type = method.DeclaringType; | ||||||
|  |  | ||||||
|  |             if (type == null) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Since .NET Core 2: | ||||||
|  |             if (StackTraceHiddenAttributeType != null) | ||||||
|  |             { | ||||||
|  |                 // Don't show any methods marked with the StackTraceHiddenAttribute | ||||||
|  |                 // https://github.com/dotnet/coreclr/pull/14652 | ||||||
|  |                 if (IsStackTraceHidden(type)) | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (type == typeof(Task<>) && method.Name == "InnerInvoke") | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             if (type == typeof(ValueTask<>) && method.Name == "get_Result") | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && method.Name.EndsWith(".GetResult")) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             if (method.Name == "GetResult" && method.DeclaringType?.FullName == "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1") | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             if (type == typeof(Task) || type.DeclaringType == typeof(Task)) | ||||||
|  |             { | ||||||
|  |                 if (method.Name.Contains(".cctor")) | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 switch (method.Name) | ||||||
|  |                 { | ||||||
|  |                     case "ExecuteWithThreadLocal": | ||||||
|  |                     case "Execute": | ||||||
|  |                     case "ExecutionContextCallback": | ||||||
|  |                     case "ExecuteEntry": | ||||||
|  |                     case "InnerInvoke": | ||||||
|  |                     case "ExecuteEntryUnsafe": | ||||||
|  |                     case "ExecuteFromThreadPool": | ||||||
|  |                         return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (type == typeof(ExecutionContext)) | ||||||
|  |             { | ||||||
|  |                 if (method.Name.Contains(".cctor")) | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 switch (method.Name) | ||||||
|  |                 { | ||||||
|  |                     case "RunInternal": | ||||||
|  |                     case "Run": | ||||||
|  |                     case "RunFromThreadPoolDispatchLoop": | ||||||
|  |                         return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (type.Namespace == "Microsoft.FSharp.Control") | ||||||
|  |             { | ||||||
|  |                 switch (type.Name) | ||||||
|  |                 { | ||||||
|  |                     case "AsyncPrimitives": | ||||||
|  |                     case "Trampoline": | ||||||
|  |                         return false; | ||||||
|  |                     case var typeName when type.IsGenericType: | ||||||
|  |                     { | ||||||
|  |                         if (typeName == "AsyncResult`1") return false; | ||||||
|  |                         else break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (type.Namespace == "Ply") | ||||||
|  |             { | ||||||
|  |                 if (type.DeclaringType?.Name == "TplPrimitives") | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Fallbacks for runtime pre-StackTraceHiddenAttribute | ||||||
|  |             if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (type == typeof(TaskAwaiter) || | ||||||
|  |                 type == typeof(TaskAwaiter<>) || | ||||||
|  |                 type == typeof(ValueTaskAwaiter) || | ||||||
|  |                 type == typeof(ValueTaskAwaiter<>) || | ||||||
|  |                 type == typeof(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter) || | ||||||
|  |                 type == typeof(ConfiguredValueTaskAwaitable<>.ConfiguredValueTaskAwaiter) || | ||||||
|  |                 type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || | ||||||
|  |                 type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) | ||||||
|  |             { | ||||||
|  |                 switch (method.Name) | ||||||
|  |                 { | ||||||
|  |                     case "HandleNonSuccessAndDebuggerNotification": | ||||||
|  |                     case "ThrowForNonSuccess": | ||||||
|  |                     case "ValidateEnd": | ||||||
|  |                     case "GetResult": | ||||||
|  |                         return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if (type.FullName == "System.ThrowHelper") | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static bool IsStackTraceHidden(MemberInfo memberInfo) | ||||||
|  |         { | ||||||
|  |             if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly) | ||||||
|  |             { | ||||||
|  |                 return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             EnumerableIList<CustomAttributeData> attributes; | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 attributes = EnumerableIList.Create(memberInfo.GetCustomAttributesData()); | ||||||
|  |             } | ||||||
|  |             catch (NotImplementedException) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var attribute in attributes) | ||||||
|  |             { | ||||||
|  |                 // reflection-only attribute, match on name | ||||||
|  |                 if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName) | ||||||
|  |                 { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // https://github.com/dotnet/runtime/blob/c985bdcec2a9190e733bcada413a193d5ff60c0d/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L375-L430 | ||||||
|  |         private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType) | ||||||
|  |         { | ||||||
|  |             if (method.DeclaringType is null) | ||||||
|  |             { | ||||||
|  |                 declaringType = null!; | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             declaringType = method.DeclaringType; | ||||||
|  |  | ||||||
|  |             var parentType = declaringType.DeclaringType; | ||||||
|  |             if (parentType is null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             static MethodInfo[] GetDeclaredMethods(Type type) => | ||||||
|  |                 type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); | ||||||
|  |  | ||||||
|  |             var methods = GetDeclaredMethods(parentType); | ||||||
|  |             if (methods == null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var candidateMethod in methods) | ||||||
|  |             { | ||||||
|  |                 var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>(inherit: false); | ||||||
|  |                 // ReSharper disable once ConditionIsAlwaysTrueOrFalse - Taken from CoreFX | ||||||
|  |                 if (attributes is null) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 bool foundAttribute = false, foundIteratorAttribute = false; | ||||||
|  |                 foreach (var asma in attributes) | ||||||
|  |                 { | ||||||
|  |                     if (asma.StateMachineType == declaringType) | ||||||
|  |                     { | ||||||
|  |                         foundAttribute = true; | ||||||
|  |                         foundIteratorAttribute |= asma is IteratorStateMachineAttribute | ||||||
|  |                             || AsyncIteratorStateMachineAttributeType?.IsInstanceOfType(asma) == true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (foundAttribute) | ||||||
|  |                 { | ||||||
|  |                     // If this is an iterator (sync or async), mark the iterator as changed, so it gets the + annotation | ||||||
|  |                     // of the original method. Non-iterator async state machines resolve directly to their builder methods | ||||||
|  |                     // so aren't marked as changed. | ||||||
|  |                     method = candidateMethod; | ||||||
|  |                     declaringType = candidateMethod.DeclaringType!; | ||||||
|  |                     return foundIteratorAttribute; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         internal enum GeneratedNameKind | ||||||
|  |         { | ||||||
|  |             None = 0, | ||||||
|  |  | ||||||
|  |             // Used by EE: | ||||||
|  |             ThisProxyField = '4', | ||||||
|  |             HoistedLocalField = '5', | ||||||
|  |             DisplayClassLocalOrField = '8', | ||||||
|  |             LambdaMethod = 'b', | ||||||
|  |             LambdaDisplayClass = 'c', | ||||||
|  |             StateMachineType = 'd', | ||||||
|  |             LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names | ||||||
|  |  | ||||||
|  |             // Used by EnC: | ||||||
|  |             AwaiterField = 'u', | ||||||
|  |             HoistedSynthesizedLocalField = 's', | ||||||
|  |  | ||||||
|  |             // Currently not parsed: | ||||||
|  |             StateMachineStateField = '1', | ||||||
|  |             IteratorCurrentBackingField = '2', | ||||||
|  |             StateMachineParameterProxyField = '3', | ||||||
|  |             ReusableHoistedLocalField = '7', | ||||||
|  |             LambdaCacheField = '9', | ||||||
|  |             FixedBufferField = 'e', | ||||||
|  |             AnonymousType = 'f', | ||||||
|  |             TransparentIdentifier = 'h', | ||||||
|  |             AnonymousTypeField = 'i', | ||||||
|  |             AutoPropertyBackingField = 'k', | ||||||
|  |             IteratorCurrentThreadIdField = 'l', | ||||||
|  |             IteratorFinallyMethod = 'm', | ||||||
|  |             BaseMethodWrapper = 'n', | ||||||
|  |             AsyncBuilderField = 't', | ||||||
|  |             DynamicCallSiteContainerType = 'o', | ||||||
|  |             DynamicCallSiteField = 'p' | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,141 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Generic.Enumerable; | ||||||
|  | using System.IO; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     public partial class EnhancedStackTrace : StackTrace, IEnumerable<EnhancedStackFrame> | ||||||
|  |     { | ||||||
|  |         public static EnhancedStackTrace Current() => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true)); | ||||||
|  |  | ||||||
|  |         private readonly List<EnhancedStackFrame> _frames; | ||||||
|  |  | ||||||
|  |         // Summary: | ||||||
|  |         //     Initializes a new instance of the System.Diagnostics.StackTrace class using the | ||||||
|  |         //     provided exception object. | ||||||
|  |         // | ||||||
|  |         // Parameters: | ||||||
|  |         //   e: | ||||||
|  |         //     The exception object from which to construct the stack trace. | ||||||
|  |         // | ||||||
|  |         // Exceptions: | ||||||
|  |         //   T:System.ArgumentNullException: | ||||||
|  |         //     The parameter e is null. | ||||||
|  |         public EnhancedStackTrace(Exception e) | ||||||
|  |         { | ||||||
|  |             if (e == null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(e)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _frames = GetFrames(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         public EnhancedStackTrace(StackTrace stackTrace) | ||||||
|  |         { | ||||||
|  |             if (stackTrace == null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(stackTrace)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _frames = GetFrames(stackTrace); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the number of frames in the stack trace. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The number of frames in the stack trace.</returns> | ||||||
|  |         public override int FrameCount => _frames.Count; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the specified stack frame. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="index">The index of the stack frame requested.</param> | ||||||
|  |         /// <returns>The specified stack frame.</returns> | ||||||
|  |         public override StackFrame GetFrame(int index) => _frames[index]; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         ///     Returns a copy of all stack frames in the current stack trace. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns> | ||||||
|  |         ///     An array of type System.Diagnostics.StackFrame representing the function calls | ||||||
|  |         ///     in the stack trace. | ||||||
|  |         /// </returns> | ||||||
|  |         public override StackFrame[] GetFrames() => _frames.ToArray(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Builds a readable representation of the stack trace. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>A readable representation of the stack trace.</returns> | ||||||
|  |         public override string ToString() | ||||||
|  |         { | ||||||
|  |             if (_frames == null || _frames.Count == 0) return ""; | ||||||
|  |  | ||||||
|  |             var sb = new StringBuilder(); | ||||||
|  |  | ||||||
|  |             Append(sb); | ||||||
|  |  | ||||||
|  |             return sb.ToString(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         internal void Append(StringBuilder sb) | ||||||
|  |         { | ||||||
|  |             var frames = _frames; | ||||||
|  |             var count = frames.Count; | ||||||
|  |  | ||||||
|  |             for (var i = 0; i < count; i++) | ||||||
|  |             { | ||||||
|  |                 if (i > 0) | ||||||
|  |                 { | ||||||
|  |                     sb.Append(Environment.NewLine); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var frame = frames[i]; | ||||||
|  |  | ||||||
|  |                 sb.Append("   at "); | ||||||
|  |                 frame.MethodInfo.Append(sb); | ||||||
|  |                  | ||||||
|  |                 if (frame.GetFileName() is {} fileName | ||||||
|  |                     // IsNullOrEmpty alone wasn't enough to disable the null warning | ||||||
|  |                     && !string.IsNullOrEmpty(fileName)) | ||||||
|  |                 { | ||||||
|  |                     sb.Append(" in "); | ||||||
|  |                     sb.Append(TryGetFullPath(fileName)); | ||||||
|  |  | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var lineNo = frame.GetFileLineNumber(); | ||||||
|  |                 if (lineNo != 0) | ||||||
|  |                 { | ||||||
|  |                     sb.Append(":line "); | ||||||
|  |                     sb.Append(lineNo); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         EnumerableIList<EnhancedStackFrame> GetEnumerator() => EnumerableIList.Create(_frames); | ||||||
|  |         IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator(); | ||||||
|  |         IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Tries to convert a given <paramref name="filePath"/> to a full path. | ||||||
|  |         /// Returns original value if the conversion isn't possible or a given path is relative. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string TryGetFullPath(string filePath) | ||||||
|  |         { | ||||||
|  |             if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile) | ||||||
|  |             { | ||||||
|  |                 return Uri.UnescapeDataString(uri.AbsolutePath); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return filePath; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,65 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | namespace System.Collections.Generic.Enumerable | ||||||
|  | { | ||||||
|  |     public static class EnumerableIList | ||||||
|  |     { | ||||||
|  |         public static EnumerableIList<T> Create<T>(IList<T> list) => new EnumerableIList<T>(list); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public struct EnumerableIList<T> : IEnumerableIList<T>, IList<T> | ||||||
|  |     { | ||||||
|  |         private readonly IList<T> _list; | ||||||
|  |  | ||||||
|  |         public EnumerableIList(IList<T> list) => _list = list; | ||||||
|  |  | ||||||
|  |         public EnumeratorIList<T> GetEnumerator() => new EnumeratorIList<T>(_list); | ||||||
|  |  | ||||||
|  |         public static implicit operator EnumerableIList<T>(List<T> list) => new EnumerableIList<T>(list); | ||||||
|  |  | ||||||
|  |         public static implicit operator EnumerableIList<T>(T[] array) => new EnumerableIList<T>(array); | ||||||
|  |  | ||||||
|  |         public static EnumerableIList<T> Empty = default; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // IList pass through | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public T this[int index] { get => _list[index]; set => _list[index] = value; } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public int Count => _list.Count; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public bool IsReadOnly => _list.IsReadOnly; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public void Add(T item) => _list.Add(item); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public void Clear() => _list.Clear(); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public bool Contains(T item) => _list.Contains(item); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public int IndexOf(T item) => _list.IndexOf(item); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public void Insert(int index, T item) => _list.Insert(index, item); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public bool Remove(T item) => _list.Remove(item); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public void RemoveAt(int index) => _list.RemoveAt(index); | ||||||
|  |  | ||||||
|  |         IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||||||
|  |  | ||||||
|  |         IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | namespace System.Collections.Generic.Enumerable | ||||||
|  | { | ||||||
|  |     public struct EnumeratorIList<T> : IEnumerator<T> | ||||||
|  |     { | ||||||
|  |         private readonly IList<T> _list; | ||||||
|  |         private int _index; | ||||||
|  |  | ||||||
|  |         public EnumeratorIList(IList<T> list) | ||||||
|  |         { | ||||||
|  |             _index = -1; | ||||||
|  |             _list = list; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public T Current => _list[_index]; | ||||||
|  |  | ||||||
|  |         public bool MoveNext() | ||||||
|  |         { | ||||||
|  |             _index++; | ||||||
|  |  | ||||||
|  |             return _index < (_list?.Count ?? 0); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() { } | ||||||
|  |         object? IEnumerator.Current => Current; | ||||||
|  |         public void Reset() => _index = -1; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | namespace System.Collections.Generic.Enumerable | ||||||
|  | { | ||||||
|  |     interface IEnumerableIList<T> : IEnumerable<T> | ||||||
|  |     { | ||||||
|  |         new EnumeratorIList<T> GetEnumerator(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,63 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Generic.Enumerable; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     public static class ExceptionExtensions | ||||||
|  |     { | ||||||
|  |         private static readonly FieldInfo? stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); | ||||||
|  |  | ||||||
|  |         private static void SetStackTracesString(this Exception exception, string value) | ||||||
|  |             => stackTraceString?.SetValue(exception, value); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree. | ||||||
|  |         /// </summary> | ||||||
|  |         public static T Demystify<T>(this T exception) where T : Exception | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 var stackTrace = new EnhancedStackTrace(exception); | ||||||
|  |  | ||||||
|  |                 if (stackTrace.FrameCount > 0) | ||||||
|  |                 { | ||||||
|  |                     exception.SetStackTracesString(stackTrace.ToString()); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (exception is AggregateException aggEx) | ||||||
|  |                 { | ||||||
|  |                     foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) | ||||||
|  |                     { | ||||||
|  |                         ex.Demystify(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 exception.InnerException?.Demystify(); | ||||||
|  |             } | ||||||
|  |             catch | ||||||
|  |             { | ||||||
|  |                 // Processing exceptions shouldn't throw exceptions; if it fails | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return exception; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets demystified string representation of the <paramref name="exception"/>. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// <see cref="Demystify{T}"/> method mutates the exception instance that can cause | ||||||
|  |         /// issues if a system relies on the stack trace be in the specific form. | ||||||
|  |         /// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first, | ||||||
|  |         /// computes a demystified string representation and then restores the original state of the exception back. | ||||||
|  |         /// </remarks> | ||||||
|  |         [Contracts.Pure] | ||||||
|  |         public static string ToStringDemystified(this Exception exception)  | ||||||
|  |             => new StringBuilder().AppendDemystified(exception).ToString(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,145 @@ | |||||||
|  | using System.Reflection; | ||||||
|  | using System.Reflection.Emit; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics.Internal | ||||||
|  | { | ||||||
|  |     internal class ILReader | ||||||
|  |     { | ||||||
|  |         private static OpCode[] singleByteOpCode; | ||||||
|  |         private static OpCode[] doubleByteOpCode; | ||||||
|  |  | ||||||
|  |         private readonly byte[] _cil; | ||||||
|  |         private int ptr; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         public ILReader(byte[] cil) => _cil = cil; | ||||||
|  |  | ||||||
|  |         public OpCode OpCode { get; private set; } | ||||||
|  |         public int MetadataToken { get; private set; } | ||||||
|  |         public MemberInfo? Operand { get; private set; } | ||||||
|  |  | ||||||
|  |         public bool Read(MethodBase methodInfo) | ||||||
|  |         { | ||||||
|  |             if (ptr < _cil.Length) | ||||||
|  |             { | ||||||
|  |                 OpCode = ReadOpCode(); | ||||||
|  |                 Operand = ReadOperand(OpCode, methodInfo); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         OpCode ReadOpCode() | ||||||
|  |         { | ||||||
|  |             var instruction = ReadByte(); | ||||||
|  |             if (instruction < 254) | ||||||
|  |                 return singleByteOpCode[instruction]; | ||||||
|  |             else | ||||||
|  |                 return doubleByteOpCode[ReadByte()]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo) | ||||||
|  |         { | ||||||
|  |             MetadataToken = 0; | ||||||
|  |             int inlineLength; | ||||||
|  |             switch (code.OperandType) | ||||||
|  |             { | ||||||
|  |                 case OperandType.InlineMethod: | ||||||
|  |                     MetadataToken = ReadInt(); | ||||||
|  |                     Type[]? methodArgs = null; | ||||||
|  |                     if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo))) | ||||||
|  |                     { | ||||||
|  |                         methodArgs = methodInfo.GetGenericArguments(); | ||||||
|  |                     } | ||||||
|  |                     Type[]? typeArgs = null; | ||||||
|  |                     if (methodInfo.DeclaringType != null) | ||||||
|  |                     { | ||||||
|  |                         typeArgs = methodInfo.DeclaringType.GetGenericArguments(); | ||||||
|  |                     } | ||||||
|  |                     try | ||||||
|  |                     { | ||||||
|  |                         return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs); | ||||||
|  |                     } | ||||||
|  |                     catch | ||||||
|  |                     { | ||||||
|  |                         // Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll | ||||||
|  |                         return null; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 case OperandType.InlineNone: | ||||||
|  |                     inlineLength = 0; | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|  |                 case OperandType.ShortInlineBrTarget: | ||||||
|  |                 case OperandType.ShortInlineVar: | ||||||
|  |                 case OperandType.ShortInlineI: | ||||||
|  |                     inlineLength = 1; | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|  |                 case OperandType.InlineVar: | ||||||
|  |                     inlineLength = 2; | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|  |                 case OperandType.InlineBrTarget: | ||||||
|  |                 case OperandType.InlineField: | ||||||
|  |                 case OperandType.InlineI: | ||||||
|  |                 case OperandType.InlineString: | ||||||
|  |                 case OperandType.InlineSig: | ||||||
|  |                 case OperandType.InlineSwitch: | ||||||
|  |                 case OperandType.InlineTok: | ||||||
|  |                 case OperandType.InlineType: | ||||||
|  |                 case OperandType.ShortInlineR: | ||||||
|  |                     inlineLength = 4; | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|  |                 case OperandType.InlineI8: | ||||||
|  |                 case OperandType.InlineR: | ||||||
|  |                     inlineLength = 8; | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|  |                 default: | ||||||
|  |                     return null; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             for (var i = 0; i < inlineLength; i++) | ||||||
|  |             { | ||||||
|  |                 ReadByte(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         byte ReadByte() => _cil[ptr++]; | ||||||
|  |  | ||||||
|  |         int ReadInt() | ||||||
|  |         { | ||||||
|  |             var b1 = ReadByte(); | ||||||
|  |             var b2 = ReadByte(); | ||||||
|  |             var b3 = ReadByte(); | ||||||
|  |             var b4 = ReadByte(); | ||||||
|  |             return b1 | b2 << 8 | b3 << 16 | b4 << 24; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         static ILReader() | ||||||
|  |         { | ||||||
|  |             singleByteOpCode = new OpCode[225]; | ||||||
|  |             doubleByteOpCode = new OpCode[31]; | ||||||
|  |  | ||||||
|  |             var fields = GetOpCodeFields(); | ||||||
|  |  | ||||||
|  |             for (var i = 0; i < fields.Length; i++) | ||||||
|  |             { | ||||||
|  |                 var code = (OpCode)fields[i].GetValue(null)!; | ||||||
|  |                 if (code.OpCodeType == OpCodeType.Nternal) | ||||||
|  |                     continue; | ||||||
|  |  | ||||||
|  |                 if (code.Size == 1) | ||||||
|  |                     singleByteOpCode[code.Value] = code; | ||||||
|  |                 else | ||||||
|  |                     doubleByteOpCode[code.Value & 0xff] = code; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         static FieldInfo[] GetOpCodeFields() => typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,136 @@ | |||||||
|  | // Copyright (c) .NET Foundation. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Reflection.Metadata; | ||||||
|  | using System.Reflection.Metadata.Ecma335; | ||||||
|  | using System.Reflection.PortableExecutable; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics.Internal | ||||||
|  | { | ||||||
|  |     // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs | ||||||
|  |     internal class PortablePdbReader : IDisposable | ||||||
|  |     { | ||||||
|  |         private readonly Dictionary<string, MetadataReaderProvider> _cache = | ||||||
|  |             new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal); | ||||||
|  |  | ||||||
|  |         public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName, out int row, out int column) | ||||||
|  |         { | ||||||
|  |             fileName = ""; | ||||||
|  |             row = 0; | ||||||
|  |             column = 0; | ||||||
|  |  | ||||||
|  |             if (method.Module.Assembly.IsDynamic) | ||||||
|  |             { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var metadataReader = GetMetadataReader(method.Module.Assembly.Location); | ||||||
|  |  | ||||||
|  |             if (metadataReader == null) | ||||||
|  |             { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var methodToken = MetadataTokens.Handle(method.MetadataToken); | ||||||
|  |  | ||||||
|  |             Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); | ||||||
|  |  | ||||||
|  |             var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); | ||||||
|  |  | ||||||
|  |             if (!handle.IsNil) | ||||||
|  |             { | ||||||
|  |                 var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); | ||||||
|  |                 var sequencePoints = methodDebugInfo.GetSequencePoints(); | ||||||
|  |                 SequencePoint? bestPointSoFar = null; | ||||||
|  |  | ||||||
|  |                 foreach (var point in sequencePoints) | ||||||
|  |                 { | ||||||
|  |                     if (point.Offset > IlOffset) | ||||||
|  |                     { | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (point.StartLine != SequencePoint.HiddenLine) | ||||||
|  |                     { | ||||||
|  |                         bestPointSoFar = point; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (bestPointSoFar.HasValue) | ||||||
|  |                 { | ||||||
|  |                     row = bestPointSoFar.Value.StartLine; | ||||||
|  |                     column = bestPointSoFar.Value.StartColumn; | ||||||
|  |                     fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private MetadataReader? GetMetadataReader(string assemblyPath) | ||||||
|  |         { | ||||||
|  |             if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null) | ||||||
|  |             { | ||||||
|  |                 var pdbPath = GetPdbPath(assemblyPath); | ||||||
|  |  | ||||||
|  |                 if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!)) | ||||||
|  |                 { | ||||||
|  |                     var pdbStream = File.OpenRead(pdbPath); | ||||||
|  |                     provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 _cache[assemblyPath] = provider; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return provider?.GetMetadataReader(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static string? GetPdbPath(string assemblyPath) | ||||||
|  |         { | ||||||
|  |             if (string.IsNullOrEmpty(assemblyPath)) | ||||||
|  |             { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (File.Exists(assemblyPath)) | ||||||
|  |             { | ||||||
|  |                 var peStream = File.OpenRead(assemblyPath); | ||||||
|  |  | ||||||
|  |                 using var peReader = new PEReader(peStream); | ||||||
|  |                 foreach (var entry in peReader.ReadDebugDirectory()) | ||||||
|  |                 { | ||||||
|  |                     if (entry.Type == DebugDirectoryEntryType.CodeView) | ||||||
|  |                     { | ||||||
|  |                         var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); | ||||||
|  |                         var peDirectory = Path.GetDirectoryName(assemblyPath); | ||||||
|  |                         return peDirectory is null  | ||||||
|  |                             ? null  | ||||||
|  |                             : Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static bool IsPortable(string pdbPath) | ||||||
|  |         { | ||||||
|  |             using var pdbStream = File.OpenRead(pdbPath); | ||||||
|  |             return pdbStream.ReadByte() == 'B' && | ||||||
|  |                    pdbStream.ReadByte() == 'S' && | ||||||
|  |                    pdbStream.ReadByte() == 'J' && | ||||||
|  |                    pdbStream.ReadByte() == 'B'; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             foreach (var entry in _cache) | ||||||
|  |             { | ||||||
|  |                 entry.Value?.Dispose(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _cache.Clear(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Threading; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics.Internal | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// A helper class that contains utilities methods for dealing with reflection. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class ReflectionHelper | ||||||
|  |     { | ||||||
|  |         private static PropertyInfo? transformerNamesLazyPropertyInfo; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns true if the <paramref name="type"/> is a value tuple type. | ||||||
|  |         /// </summary> | ||||||
|  |         public static bool IsValueTuple(this Type type) | ||||||
|  |         { | ||||||
|  |             return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns true if the given <paramref name="attribute"/> is of type <code>TupleElementNameAttribute</code>. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically that  | ||||||
|  |         /// the given <paramref name="attribute"/> is <code>TupleElementNameAttribute</code>. | ||||||
|  |         /// </remarks> | ||||||
|  |         public static bool IsTupleElementNameAttribute(this Attribute attribute) | ||||||
|  |         { | ||||||
|  |             var attributeType = attribute.GetType(); | ||||||
|  |             return attributeType.Namespace == "System.Runtime.CompilerServices" && | ||||||
|  |                    attributeType.Name == "TupleElementNamesAttribute"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns 'TransformNames' property value from a given <paramref name="attribute"/>. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection  | ||||||
|  |         /// instead of casting the attribute to a specific type. | ||||||
|  |         /// </remarks> | ||||||
|  |         public static IList<string>? GetTransformerNames(this Attribute attribute) | ||||||
|  |         { | ||||||
|  |             Debug.Assert(attribute.IsTupleElementNameAttribute()); | ||||||
|  |  | ||||||
|  |             var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); | ||||||
|  |             return propertyInfo?.GetValue(attribute) as IList<string>; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static PropertyInfo? GetTransformNamesPropertyInfo(Type attributeType) | ||||||
|  |         { | ||||||
|  | #pragma warning disable 8634 | ||||||
|  |             return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo, | ||||||
|  | #pragma warning restore 8634 | ||||||
|  |                 () => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,172 @@ | |||||||
|  | // Copyright (c) .NET Foundation. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic.Enumerable; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     public class ResolvedMethod | ||||||
|  |     { | ||||||
|  |         public MethodBase? MethodBase { get; set; } | ||||||
|  |  | ||||||
|  |         public Type? DeclaringType { get; set; } | ||||||
|  |          | ||||||
|  |         public bool IsAsync { get; set; } | ||||||
|  |  | ||||||
|  |         public bool IsLambda { get; set; } | ||||||
|  |  | ||||||
|  |         public ResolvedParameter? ReturnParameter { get; set; } | ||||||
|  |  | ||||||
|  |         public string? Name { get; set; } | ||||||
|  |  | ||||||
|  |         public int? Ordinal { get; set; } | ||||||
|  |  | ||||||
|  |         public string? GenericArguments { get; set; } | ||||||
|  |  | ||||||
|  |         public Type[]? ResolvedGenericArguments { get; set; } | ||||||
|  |  | ||||||
|  |         public MethodBase? SubMethodBase { get; set; } | ||||||
|  |  | ||||||
|  |         public string? SubMethod { get; set; } | ||||||
|  |  | ||||||
|  |         public EnumerableIList<ResolvedParameter> Parameters { get; set; } | ||||||
|  |  | ||||||
|  |         public EnumerableIList<ResolvedParameter> SubMethodParameters { get; set; } | ||||||
|  |         public int RecurseCount { get; internal set; } | ||||||
|  |  | ||||||
|  |         internal bool IsSequentialEquivalent(ResolvedMethod obj) | ||||||
|  |         { | ||||||
|  |             return  | ||||||
|  |                 IsAsync == obj.IsAsync &&  | ||||||
|  |                 DeclaringType == obj.DeclaringType && | ||||||
|  |                 Name == obj.Name && | ||||||
|  |                 IsLambda == obj.IsLambda && | ||||||
|  |                 Ordinal == obj.Ordinal && | ||||||
|  |                 GenericArguments == obj.GenericArguments && | ||||||
|  |                 SubMethod == obj.SubMethod; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override string ToString() => Append(new StringBuilder()).ToString(); | ||||||
|  |  | ||||||
|  |         public StringBuilder Append(StringBuilder builder) | ||||||
|  |             => Append(builder, true); | ||||||
|  |  | ||||||
|  |         public StringBuilder Append(StringBuilder builder, bool fullName) | ||||||
|  |         { | ||||||
|  |             if (IsAsync) | ||||||
|  |             { | ||||||
|  |                 builder.Append("async "); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (ReturnParameter != null) | ||||||
|  |             { | ||||||
|  |                 ReturnParameter.Append(builder); | ||||||
|  |                 builder.Append(' '); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (DeclaringType != null) | ||||||
|  |             { | ||||||
|  |  | ||||||
|  |                 if (Name == ".ctor") | ||||||
|  |                 { | ||||||
|  |                     if (string.IsNullOrEmpty(SubMethod) && !IsLambda) | ||||||
|  |                         builder.Append("new "); | ||||||
|  |  | ||||||
|  |                     AppendDeclaringTypeName(builder, fullName); | ||||||
|  |                 } | ||||||
|  |                 else if (Name == ".cctor") | ||||||
|  |                 { | ||||||
|  |                     builder.Append("static "); | ||||||
|  |                     AppendDeclaringTypeName(builder, fullName); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     AppendDeclaringTypeName(builder, fullName) | ||||||
|  |                         .Append('.') | ||||||
|  |                         .Append(Name); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 builder.Append(Name); | ||||||
|  |             } | ||||||
|  |             builder.Append(GenericArguments); | ||||||
|  |  | ||||||
|  |             builder.Append('('); | ||||||
|  |             if (MethodBase != null) | ||||||
|  |             { | ||||||
|  |                 var isFirst = true; | ||||||
|  |                 foreach(var param in Parameters) | ||||||
|  |                 { | ||||||
|  |                     if (isFirst) | ||||||
|  |                     { | ||||||
|  |                         isFirst = false; | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         builder.Append(", "); | ||||||
|  |                     } | ||||||
|  |                     param.Append(builder); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 builder.Append('?'); | ||||||
|  |             } | ||||||
|  |             builder.Append(')'); | ||||||
|  |  | ||||||
|  |             if (!string.IsNullOrEmpty(SubMethod) || IsLambda) | ||||||
|  |             { | ||||||
|  |                 builder.Append('+'); | ||||||
|  |                 builder.Append(SubMethod); | ||||||
|  |                 builder.Append('('); | ||||||
|  |                 if (SubMethodBase != null) | ||||||
|  |                 { | ||||||
|  |                     var isFirst = true; | ||||||
|  |                     foreach (var param in SubMethodParameters) | ||||||
|  |                     { | ||||||
|  |                         if (isFirst) | ||||||
|  |                         { | ||||||
|  |                             isFirst = false; | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             builder.Append(", "); | ||||||
|  |                         } | ||||||
|  |                         param.Append(builder); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     builder.Append('?'); | ||||||
|  |                 } | ||||||
|  |                 builder.Append(')'); | ||||||
|  |                 if (IsLambda) | ||||||
|  |                 { | ||||||
|  |                     builder.Append(" => { }"); | ||||||
|  |  | ||||||
|  |                     if (Ordinal.HasValue) | ||||||
|  |                     { | ||||||
|  |                         builder.Append(" ["); | ||||||
|  |                         builder.Append(Ordinal); | ||||||
|  |                         builder.Append(']'); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (RecurseCount > 0) | ||||||
|  |             { | ||||||
|  |                 builder.Append($" x {RecurseCount + 1:0}"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return builder; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true) | ||||||
|  |         { | ||||||
|  |             return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: fullName, includeGenericParameterNames: true) : builder; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     public class ResolvedParameter | ||||||
|  |     { | ||||||
|  |         public string? Name { get; set; } | ||||||
|  |  | ||||||
|  |         public Type ResolvedType { get; set; } | ||||||
|  |  | ||||||
|  |         public string? Prefix { get; set; } | ||||||
|  |         public bool IsDynamicType { get; set; } | ||||||
|  |  | ||||||
|  |         public ResolvedParameter(Type resolvedType) => ResolvedType = resolvedType; | ||||||
|  |  | ||||||
|  |         public override string ToString() => Append(new StringBuilder()).ToString(); | ||||||
|  |  | ||||||
|  |         public StringBuilder Append(StringBuilder sb) | ||||||
|  |         { | ||||||
|  |             if (ResolvedType.Assembly.ManifestModule.Name == "FSharp.Core.dll" && ResolvedType.Name == "Unit") | ||||||
|  |                 return sb; | ||||||
|  |              | ||||||
|  |             if (!string.IsNullOrEmpty(Prefix)) | ||||||
|  |             { | ||||||
|  |                 sb.Append(Prefix) | ||||||
|  |                   .Append(' '); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (IsDynamicType) | ||||||
|  |             { | ||||||
|  |                 sb.Append("dynamic"); | ||||||
|  |             } | ||||||
|  |             else if (ResolvedType != null) | ||||||
|  |             { | ||||||
|  |                 AppendTypeName(sb); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 sb.Append('?'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (!string.IsNullOrEmpty(Name)) | ||||||
|  |             { | ||||||
|  |                 sb.Append(' ') | ||||||
|  |                   .Append(Name); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return sb; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected virtual void AppendTypeName(StringBuilder sb)  | ||||||
|  |         { | ||||||
|  |             sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic.Enumerable; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics  | ||||||
|  | { | ||||||
|  |     public static class StringBuilderExtentions | ||||||
|  |     { | ||||||
|  |         public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 var stackTrace = new EnhancedStackTrace(exception); | ||||||
|  |  | ||||||
|  |                 builder.Append(exception.GetType()); | ||||||
|  |                 if (!string.IsNullOrEmpty(exception.Message)) | ||||||
|  |                 { | ||||||
|  |                     builder.Append(": ").Append(exception.Message); | ||||||
|  |                 } | ||||||
|  |                 builder.Append(Environment.NewLine); | ||||||
|  |  | ||||||
|  |                 if (stackTrace.FrameCount > 0) | ||||||
|  |                 { | ||||||
|  |                     stackTrace.Append(builder); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (exception is AggregateException aggEx) | ||||||
|  |                 { | ||||||
|  |                     foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) | ||||||
|  |                     { | ||||||
|  |                         builder.AppendInnerException(ex); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (exception.InnerException != null) | ||||||
|  |                 { | ||||||
|  |                     builder.AppendInnerException(exception.InnerException); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch | ||||||
|  |             { | ||||||
|  |                 // Processing exceptions shouldn't throw exceptions; if it fails | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return builder; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void AppendInnerException(this StringBuilder builder, Exception exception)  | ||||||
|  |             => builder.Append(" ---> ") | ||||||
|  |                 .AppendDemystified(exception) | ||||||
|  |                 .AppendLine() | ||||||
|  |                 .Append("   --- End of inner exception stack trace ---"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,218 @@ | |||||||
|  | // Copyright (c) .NET Foundation. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics | ||||||
|  | { | ||||||
|  |     // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs | ||||||
|  |     public static class TypeNameHelper | ||||||
|  |     { | ||||||
|  |         public static readonly Dictionary<Type, string> BuiltInTypeNames = new Dictionary<Type, string> | ||||||
|  |         { | ||||||
|  |             { typeof(void), "void" }, | ||||||
|  |             { typeof(bool), "bool" }, | ||||||
|  |             { typeof(byte), "byte" }, | ||||||
|  |             { typeof(char), "char" }, | ||||||
|  |             { typeof(decimal), "decimal" }, | ||||||
|  |             { typeof(double), "double" }, | ||||||
|  |             { typeof(float), "float" }, | ||||||
|  |             { typeof(int), "int" }, | ||||||
|  |             { typeof(long), "long" }, | ||||||
|  |             { typeof(object), "object" }, | ||||||
|  |             { typeof(sbyte), "sbyte" }, | ||||||
|  |             { typeof(short), "short" }, | ||||||
|  |             { typeof(string), "string" }, | ||||||
|  |             { typeof(uint), "uint" }, | ||||||
|  |             { typeof(ulong), "ulong" }, | ||||||
|  |             { typeof(ushort), "ushort" } | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         public static readonly Dictionary<string, string> FSharpTypeNames = new Dictionary<string, string> | ||||||
|  |         { | ||||||
|  |             { "Unit", "void" }, | ||||||
|  |             { "FSharpOption", "Option" }, | ||||||
|  |             { "FSharpAsync", "Async" }, | ||||||
|  |             { "FSharpOption`1", "Option" }, | ||||||
|  |             { "FSharpAsync`1", "Async" } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Pretty print a type name. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="type">The <see cref="Type"/>.</param> | ||||||
|  |         /// <param name="fullName"><c>true</c> to print a fully qualified name.</param> | ||||||
|  |         /// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param> | ||||||
|  |         /// <returns>The pretty printed type name.</returns> | ||||||
|  |         public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) | ||||||
|  |         { | ||||||
|  |             var builder = new StringBuilder(); | ||||||
|  |             ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); | ||||||
|  |             return builder.ToString(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true, bool includeGenericParameterNames = false) | ||||||
|  |         { | ||||||
|  |             ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); | ||||||
|  |             return builder; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns a name of given generic type without '`'. | ||||||
|  |         /// </summary> | ||||||
|  |         public static string GetTypeNameForGenericType(Type type) | ||||||
|  |         { | ||||||
|  |             if (!type.IsGenericType) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentException("The given type should be generic", nameof(type)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var genericPartIndex = type.Name.IndexOf('`'); | ||||||
|  |  | ||||||
|  |             return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) | ||||||
|  |         { | ||||||
|  |             if (type.IsGenericType) | ||||||
|  |             { | ||||||
|  |                 var underlyingType = Nullable.GetUnderlyingType(type); | ||||||
|  |                 if (underlyingType != null) | ||||||
|  |                 { | ||||||
|  |                     ProcessType(builder, underlyingType, options); | ||||||
|  |                     builder.Append('?'); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     var genericArguments = type.GetGenericArguments(); | ||||||
|  |                     ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if (type.IsArray) | ||||||
|  |             { | ||||||
|  |                 ProcessArrayType(builder, type, options); | ||||||
|  |             } | ||||||
|  |             else if (BuiltInTypeNames.TryGetValue(type, out var builtInName)) | ||||||
|  |             { | ||||||
|  |                 builder.Append(builtInName); | ||||||
|  |             } | ||||||
|  |             else if (type.Namespace == nameof(System)) | ||||||
|  |             { | ||||||
|  |                 builder.Append(type.Name); | ||||||
|  |             } | ||||||
|  |             else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"  | ||||||
|  |                      && FSharpTypeNames.TryGetValue(type.Name, out builtInName)) | ||||||
|  |             { | ||||||
|  |                 builder.Append(builtInName); | ||||||
|  |             } | ||||||
|  |             else if (type.IsGenericParameter) | ||||||
|  |             { | ||||||
|  |                 if (options.IncludeGenericParameterNames) | ||||||
|  |                 { | ||||||
|  |                     builder.Append(type.Name); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) | ||||||
|  |         { | ||||||
|  |             var innerType = type; | ||||||
|  |             while (innerType.IsArray) | ||||||
|  |             { | ||||||
|  |                 if (innerType.GetElementType() is { } inner) | ||||||
|  |                 { | ||||||
|  |                     innerType = inner; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ProcessType(builder, innerType, options); | ||||||
|  |  | ||||||
|  |             while (type.IsArray) | ||||||
|  |             { | ||||||
|  |                 builder.Append('['); | ||||||
|  |                 builder.Append(',', type.GetArrayRank() - 1); | ||||||
|  |                 builder.Append(']'); | ||||||
|  |                 if (type.GetElementType() is not { } elementType) | ||||||
|  |                 { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 type = elementType; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) | ||||||
|  |         { | ||||||
|  |             var offset = 0; | ||||||
|  |             if (type.IsNested && type.DeclaringType is not null) | ||||||
|  |             { | ||||||
|  |                 offset = type.DeclaringType.GetGenericArguments().Length; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (options.FullName) | ||||||
|  |             { | ||||||
|  |                 if (type.IsNested && type.DeclaringType is not null) | ||||||
|  |                 { | ||||||
|  |                     ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); | ||||||
|  |                     builder.Append('+'); | ||||||
|  |                 } | ||||||
|  |                 else if (!string.IsNullOrEmpty(type.Namespace)) | ||||||
|  |                 { | ||||||
|  |                     builder.Append(type.Namespace); | ||||||
|  |                     builder.Append('.'); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var genericPartIndex = type.Name.IndexOf('`'); | ||||||
|  |             if (genericPartIndex <= 0) | ||||||
|  |             { | ||||||
|  |                 builder.Append(type.Name); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"  | ||||||
|  |                      && FSharpTypeNames.TryGetValue(type.Name, out var builtInName)) | ||||||
|  |             { | ||||||
|  |                 builder.Append(builtInName); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 builder.Append(type.Name, 0, genericPartIndex); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             builder.Append('<'); | ||||||
|  |             for (var i = offset; i < length; i++) | ||||||
|  |             { | ||||||
|  |                 ProcessType(builder, genericArguments[i], options); | ||||||
|  |                 if (i + 1 == length) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 builder.Append(','); | ||||||
|  |                 if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) | ||||||
|  |                 { | ||||||
|  |                     builder.Append(' '); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             builder.Append('>'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private struct DisplayNameOptions | ||||||
|  |         { | ||||||
|  |             public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) | ||||||
|  |             { | ||||||
|  |                 FullName = fullName; | ||||||
|  |                 IncludeGenericParameterNames = includeGenericParameterNames; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             public bool FullName { get; } | ||||||
|  |  | ||||||
|  |             public bool IncludeGenericParameterNames { get; } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | // Copyright (c) Ben A Adams. All rights reserved. | ||||||
|  | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||||||
|  |  | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Diagnostics.Internal; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace System.Diagnostics  | ||||||
|  | { | ||||||
|  |     public class ValueTupleResolvedParameter : ResolvedParameter | ||||||
|  |     { | ||||||
|  |         public IList<string> TupleNames { get; } | ||||||
|  |  | ||||||
|  |         public ValueTupleResolvedParameter(Type resolvedType, IList<string> tupleNames)  | ||||||
|  |             : base(resolvedType)  | ||||||
|  |             => TupleNames = tupleNames; | ||||||
|  |  | ||||||
|  |         protected override void AppendTypeName(StringBuilder sb) | ||||||
|  |         { | ||||||
|  |             if (ResolvedType is not null) | ||||||
|  |             { | ||||||
|  |                 if (ResolvedType.IsValueTuple()) | ||||||
|  |                 { | ||||||
|  |                     AppendValueTupleParameterName(sb, ResolvedType); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     // Need to unwrap the first generic argument first. | ||||||
|  |                     sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType)); | ||||||
|  |                     sb.Append('<'); | ||||||
|  |                     AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]); | ||||||
|  |                     sb.Append('>'); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType) | ||||||
|  |         { | ||||||
|  |             sb.Append('('); | ||||||
|  |             var args = parameterType.GetGenericArguments(); | ||||||
|  |             for (var i = 0; i < args.Length; i++) | ||||||
|  |             { | ||||||
|  |                 if (i > 0) | ||||||
|  |                 { | ||||||
|  |                     sb.Append(", "); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true); | ||||||
|  |  | ||||||
|  |                 if (i >= TupleNames.Count) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var argName = TupleNames[i]; | ||||||
|  |                 if (argName == null) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 sb.Append(' '); | ||||||
|  |                 sb.Append(argName); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             sb.Append(')'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -23,15 +23,13 @@ | |||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<None Remove="FriendlyException\Assets\error.html" /> | 		<None Remove="FriendlyException\Assets\error.html" /> | ||||||
| 		<None Remove="SpecificationDocument\Assets\index.html" /> | 		<None Remove="SpecificationDocument\Assets\index.html" /> | ||||||
|  | 		<None Remove="Schedule\Dashboard\frontend\**\*" /> | ||||||
| 		<EmbeddedResource Include="FriendlyException\Assets\error.html" /> | 		<EmbeddedResource Include="FriendlyException\Assets\error.html" /> | ||||||
| 		<EmbeddedResource Include="SpecificationDocument\Assets\index.html" /> | 		<EmbeddedResource Include="SpecificationDocument\Assets\index.html" /> | ||||||
|  | 		<EmbeddedResource Include="Schedule\Dashboard\frontend\**\*" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
| 		<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> |  | ||||||
| 		<!--<PackageReference Include="FastExpressionCompiler" Version="5.3.0" />--> |  | ||||||
| 		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> |  | ||||||
| 		<!--<PackageReference Include="Mapster" Version="7.4.0" />--> |  | ||||||
| 		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" /> | 		<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" /> | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
| @@ -39,15 +37,12 @@ | |||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET8Version)" /> | 		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET8Version)" /> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET8Version)" /> | 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET8Version)" /> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" /> | 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" /> | ||||||
| 		<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.1" /> |  | ||||||
| 		<PackageReference Include="System.Text.Json" Version="8.0.5" /> |  | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  | 	 | ||||||
| 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | 	<ItemGroup Condition=" '$(TargetFramework)' == 'net9.0' "> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(NET9Version)" /> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(NET9Version)" /> | ||||||
| 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET9Version)" /> | 		<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(NET9Version)" /> | ||||||
| 		<PackageReference Include="System.Reflection.MetadataLoadContext" Version="$(NET9Version)" /> |  | ||||||
| 		<PackageReference Include="System.Text.Json" Version="$(NET9Version)" /> |  | ||||||
|  |  | ||||||
| 	</ItemGroup> | 	</ItemGroup> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -94,6 +94,11 @@ public sealed class HttpFileDownloadBuilder | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal Type? FileTransferEventHandlerType { get; private set; } |     internal Type? FileTransferEventHandlerType { get; private set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     <see cref="HttpRequestBuilder" /> 配置委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<HttpRequestBuilder>? RequestConfigure { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     设置用于传输操作的缓冲区大小 |     ///     设置用于传输操作的缓冲区大小 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -302,17 +307,50 @@ public sealed class HttpFileDownloadBuilder | |||||||
|         where TFileTransferEventHandler : IHttpFileTransferEventHandler => |         where TFileTransferEventHandler : IHttpFileTransferEventHandler => | ||||||
|         SetEventHandler(typeof(TFileTransferEventHandler)); |         SetEventHandler(typeof(TFileTransferEventHandler)); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     配置 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>支持多次调用。</remarks> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="HttpFileDownloadBuilder" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public HttpFileDownloadBuilder WithRequest(Action<HttpRequestBuilder> configure) | ||||||
|  |     { | ||||||
|  |         // 空检查 | ||||||
|  |         ArgumentNullException.ThrowIfNull(configure); | ||||||
|  |  | ||||||
|  |         // 如果 RequestConfigure 未设置则直接赋值 | ||||||
|  |         if (RequestConfigure is null) | ||||||
|  |         { | ||||||
|  |             RequestConfigure = configure; | ||||||
|  |         } | ||||||
|  |         // 否则创建级联调用委托 | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             // 复制一个新的委托避免死循环 | ||||||
|  |             var originalRequestConfigure = RequestConfigure; | ||||||
|  |  | ||||||
|  |             RequestConfigure = httpRequestBuilder => | ||||||
|  |             { | ||||||
|  |                 originalRequestConfigure.Invoke(httpRequestBuilder); | ||||||
|  |                 configure.Invoke(httpRequestBuilder); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     构建 <see cref="HttpRequestBuilder" /> 实例 |     ///     构建 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="httpRemoteOptions"> |     /// <param name="httpRemoteOptions"> | ||||||
|     ///     <see cref="HttpRemoteOptions" /> |     ///     <see cref="HttpRemoteOptions" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpRequestBuilder" /> |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions, Action<HttpRequestBuilder>? configure = null) |     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteOptions); |         ArgumentNullException.ThrowIfNull(httpRemoteOptions); | ||||||
| @@ -335,7 +373,7 @@ public sealed class HttpFileDownloadBuilder | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 调用自定义配置委托 |         // 调用自定义配置委托 | ||||||
|         configure?.Invoke(httpRequestBuilder); |         RequestConfigure?.Invoke(httpRequestBuilder); | ||||||
|  |  | ||||||
|         return httpRequestBuilder; |         return httpRequestBuilder; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -118,6 +118,11 @@ public sealed class HttpFileUploadBuilder | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal Type? FileTransferEventHandlerType { get; private set; } |     internal Type? FileTransferEventHandlerType { get; private set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     <see cref="HttpRequestBuilder" /> 配置委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<HttpRequestBuilder>? RequestConfigure { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     设置内容类型(文件类型) |     ///     设置内容类型(文件类型) | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -321,6 +326,40 @@ public sealed class HttpFileUploadBuilder | |||||||
|         where TFileTransferEventHandler : IHttpFileTransferEventHandler => |         where TFileTransferEventHandler : IHttpFileTransferEventHandler => | ||||||
|         SetEventHandler(typeof(TFileTransferEventHandler)); |         SetEventHandler(typeof(TFileTransferEventHandler)); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     配置 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>支持多次调用。</remarks> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="HttpFileUploadBuilder" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public HttpFileUploadBuilder WithRequest(Action<HttpRequestBuilder> configure) | ||||||
|  |     { | ||||||
|  |         // 空检查 | ||||||
|  |         ArgumentNullException.ThrowIfNull(configure); | ||||||
|  |  | ||||||
|  |         // 如果 RequestConfigure 未设置则直接赋值 | ||||||
|  |         if (RequestConfigure is null) | ||||||
|  |         { | ||||||
|  |             RequestConfigure = configure; | ||||||
|  |         } | ||||||
|  |         // 否则创建级联调用委托 | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             // 复制一个新的委托避免死循环 | ||||||
|  |             var originalRequestConfigure = RequestConfigure; | ||||||
|  |  | ||||||
|  |             RequestConfigure = httpRequestBuilder => | ||||||
|  |             { | ||||||
|  |                 originalRequestConfigure.Invoke(httpRequestBuilder); | ||||||
|  |                 configure.Invoke(httpRequestBuilder); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     构建 <see cref="HttpRequestBuilder" /> 实例 |     ///     构建 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -328,13 +367,11 @@ public sealed class HttpFileUploadBuilder | |||||||
|     ///     <see cref="HttpRemoteOptions" /> |     ///     <see cref="HttpRemoteOptions" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="progressChannel">文件传输进度信息的通道</param> |     /// <param name="progressChannel">文件传输进度信息的通道</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpRequestBuilder" /> |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions, |     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions, | ||||||
|         Channel<FileTransferProgress> progressChannel, |         Channel<FileTransferProgress> progressChannel) | ||||||
|         Action<HttpRequestBuilder>? configure = null) |  | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteOptions); |         ArgumentNullException.ThrowIfNull(httpRemoteOptions); | ||||||
| @@ -355,7 +392,7 @@ public sealed class HttpFileUploadBuilder | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 调用自定义配置委托 |         // 调用自定义配置委托 | ||||||
|         configure?.Invoke(httpRequestBuilder); |         RequestConfigure?.Invoke(httpRequestBuilder); | ||||||
|  |  | ||||||
|         return httpRequestBuilder; |         return httpRequestBuilder; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -79,6 +79,11 @@ public sealed class HttpLongPollingBuilder | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal Type? LongPollingEventHandlerType { get; private set; } |     internal Type? LongPollingEventHandlerType { get; private set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     <see cref="HttpRequestBuilder" /> 配置委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<HttpRequestBuilder>? RequestConfigure { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     设置轮询重试间隔 |     ///     设置轮询重试间隔 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -245,17 +250,50 @@ public sealed class HttpLongPollingBuilder | |||||||
|         where TLongPollingEventHandler : IHttpLongPollingEventHandler => |         where TLongPollingEventHandler : IHttpLongPollingEventHandler => | ||||||
|         SetEventHandler(typeof(TLongPollingEventHandler)); |         SetEventHandler(typeof(TLongPollingEventHandler)); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     配置 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>支持多次调用。</remarks> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="HttpLongPollingBuilder" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public HttpLongPollingBuilder WithRequest(Action<HttpRequestBuilder> configure) | ||||||
|  |     { | ||||||
|  |         // 空检查 | ||||||
|  |         ArgumentNullException.ThrowIfNull(configure); | ||||||
|  |  | ||||||
|  |         // 如果 RequestConfigure 未设置则直接赋值 | ||||||
|  |         if (RequestConfigure is null) | ||||||
|  |         { | ||||||
|  |             RequestConfigure = configure; | ||||||
|  |         } | ||||||
|  |         // 否则创建级联调用委托 | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             // 复制一个新的委托避免死循环 | ||||||
|  |             var originalRequestConfigure = RequestConfigure; | ||||||
|  |  | ||||||
|  |             RequestConfigure = httpRequestBuilder => | ||||||
|  |             { | ||||||
|  |                 originalRequestConfigure.Invoke(httpRequestBuilder); | ||||||
|  |                 configure.Invoke(httpRequestBuilder); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     构建 <see cref="HttpRequestBuilder" /> 实例 |     ///     构建 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="httpRemoteOptions"> |     /// <param name="httpRemoteOptions"> | ||||||
|     ///     <see cref="HttpRemoteOptions" /> |     ///     <see cref="HttpRemoteOptions" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpRequestBuilder" /> |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions, Action<HttpRequestBuilder>? configure = null) |     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteOptions); |         ArgumentNullException.ThrowIfNull(httpRemoteOptions); | ||||||
| @@ -277,7 +315,7 @@ public sealed class HttpLongPollingBuilder | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 调用自定义配置委托 |         // 调用自定义配置委托 | ||||||
|         configure?.Invoke(httpRequestBuilder); |         RequestConfigure?.Invoke(httpRequestBuilder); | ||||||
|  |  | ||||||
|         return httpRequestBuilder; |         return httpRequestBuilder; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -239,9 +239,7 @@ public sealed class HttpMultipartFormDataBuilder | |||||||
|         { |         { | ||||||
|             _partContents.Add(new MultipartFormDataItem(name) |             _partContents.Add(new MultipartFormDataItem(name) | ||||||
|             { |             { | ||||||
|                 ContentType = mediaType, |                 ContentType = mediaType, RawContent = rawObject, ContentEncoding = encoding | ||||||
|                 RawContent = rawObject, |  | ||||||
|                 ContentEncoding = encoding |  | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             return this; |             return this; | ||||||
| @@ -250,14 +248,27 @@ public sealed class HttpMultipartFormDataBuilder | |||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(rawObject); |         ArgumentNullException.ThrowIfNull(rawObject); | ||||||
|  |  | ||||||
|         // 将对象转换为 MultipartFormDataItem 集合再追加 |         // 将对象转换为字典集合再追加 | ||||||
|         _partContents.AddRange(rawObject.ObjectToDictionary()!.Select(u => |         var formDataItems = rawObject.ObjectToDictionary()!; | ||||||
|             new MultipartFormDataItem(u.Key.ToCultureString(CultureInfo.InvariantCulture)!) |  | ||||||
|  |         // 遍历字典集合并逐条追加 | ||||||
|  |         foreach (var (key, rawContent) in formDataItems) | ||||||
|  |         { | ||||||
|  |             // 检查原始请求内容是否是 MultipartFile 类型 | ||||||
|  |             if (rawContent is MultipartFile multipartFile) | ||||||
|             { |             { | ||||||
|                 ContentType = MediaTypeNames.Text.Plain, |                 AddFile(multipartFile); | ||||||
|                 RawContent = u.Value, |             } | ||||||
|                 ContentEncoding = encoding |             else | ||||||
|             })); |             { | ||||||
|  |                 _partContents.Add(new MultipartFormDataItem(key.ToCultureString(CultureInfo.InvariantCulture)!) | ||||||
|  |                 { | ||||||
|  |                     ContentType = Helpers.GetContentTypeOrDefault(rawContent, MediaTypeNames.Text.Plain), | ||||||
|  |                     RawContent = rawContent, | ||||||
|  |                     ContentEncoding = encoding | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| @@ -515,10 +526,7 @@ public sealed class HttpMultipartFormDataBuilder | |||||||
|  |  | ||||||
|         _partContents.Add(new MultipartFormDataItem(name) |         _partContents.Add(new MultipartFormDataItem(name) | ||||||
|         { |         { | ||||||
|             ContentType = mimeType, |             ContentType = mimeType, RawContent = stream, ContentEncoding = encoding, FileName = fileName | ||||||
|             RawContent = stream, |  | ||||||
|             ContentEncoding = encoding, |  | ||||||
|             FileName = fileName |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // 是否在请求结束后自动释放流 |         // 是否在请求结束后自动释放流 | ||||||
| @@ -561,10 +569,7 @@ public sealed class HttpMultipartFormDataBuilder | |||||||
|  |  | ||||||
|         _partContents.Add(new MultipartFormDataItem(name) |         _partContents.Add(new MultipartFormDataItem(name) | ||||||
|         { |         { | ||||||
|             ContentType = mimeType, |             ContentType = mimeType, RawContent = byteArray, ContentEncoding = encoding, FileName = fileName | ||||||
|             RawContent = byteArray, |  | ||||||
|             ContentEncoding = encoding, |  | ||||||
|             FileName = fileName |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         return this; |         return this; | ||||||
| @@ -687,9 +692,7 @@ public sealed class HttpMultipartFormDataBuilder | |||||||
|  |  | ||||||
|         _partContents.Add(new MultipartFormDataItem(formName) |         _partContents.Add(new MultipartFormDataItem(formName) | ||||||
|         { |         { | ||||||
|             ContentType = mediaType, |             ContentType = mediaType, RawContent = httpContent, ContentEncoding = encoding | ||||||
|             RawContent = httpContent, |  | ||||||
|             ContentEncoding = encoding |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         return this; |         return this; | ||||||
| @@ -815,7 +818,12 @@ public sealed class HttpMultipartFormDataBuilder | |||||||
|                 new ContentDispositionHeaderValue(Constants.FORM_DATA_DISPOSITION_TYPE) |                 new ContentDispositionHeaderValue(Constants.FORM_DATA_DISPOSITION_TYPE) | ||||||
|                 { |                 { | ||||||
|                     Name = multipartFormDataItem.Name.AddQuotes(), |                     Name = multipartFormDataItem.Name.AddQuotes(), | ||||||
|                     FileName = multipartFormDataItem.FileName.AddQuotes() |                     FileName = | ||||||
|  |                         (string.IsNullOrWhiteSpace(multipartFormDataItem.FileName) && | ||||||
|  |                          contentType.IsIn([MediaTypeNames.Application.Octet], StringComparer.OrdinalIgnoreCase) | ||||||
|  |                             // 解决发送文件或二进制【未设置】文件名问题 | ||||||
|  |                             ? $"Unnamed_{DateTime.Now:yyyyMMddHHmmss}_{Guid.NewGuid().ToString("N")[..8]}" | ||||||
|  |                             : multipartFormDataItem.FileName).AddQuotes() | ||||||
|                 }; |                 }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -280,13 +280,28 @@ public sealed partial class HttpRequestBuilder | |||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpRequestBuilder" /> |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     public HttpRequestBuilder SetMultipartContent(Action<HttpMultipartFormDataBuilder> configure) |     public HttpRequestBuilder SetMultipartContent(Action<HttpMultipartFormDataBuilder> configure) => | ||||||
|  |         SetMultipartContent(configure, true); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     设置多部分表单内容,请求类型为 <c>multipart/form-data</c> | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks> | ||||||
|  |     ///     该操作将强制覆盖 <see cref="SetContent" />、<see cref="SetContentEncoding(System.Text.Encoding)" /> 和 | ||||||
|  |     ///     <see cref="SetContentType" /> 设置的内容。 | ||||||
|  |     /// </remarks> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|  |     /// <param name="omitContentType">是否移除默认的多部分内容的 <c>Content-Type</c></param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public HttpRequestBuilder SetMultipartContent(Action<HttpMultipartFormDataBuilder> configure, bool omitContentType) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(configure); |         ArgumentNullException.ThrowIfNull(configure); | ||||||
|  |  | ||||||
|         // 初始化 HttpMultipartFormDataBuilder 实例 |         // 初始化 HttpMultipartFormDataBuilder 实例 | ||||||
|         var httpMultipartFormDataBuilder = new HttpMultipartFormDataBuilder(this); |         var httpMultipartFormDataBuilder = new HttpMultipartFormDataBuilder(this) { OmitContentType = omitContentType }; | ||||||
|  |  | ||||||
|         // 调用自定义配置委托 |         // 调用自定义配置委托 | ||||||
|         configure.Invoke(httpMultipartFormDataBuilder); |         configure.Invoke(httpMultipartFormDataBuilder); | ||||||
|   | |||||||
| @@ -337,9 +337,7 @@ public sealed partial class HttpRequestBuilder | |||||||
|         { |         { | ||||||
|             httpRequestMessage.Headers.CacheControl = new CacheControlHeaderValue |             httpRequestMessage.Headers.CacheControl = new CacheControlHeaderValue | ||||||
|             { |             { | ||||||
|                 NoCache = true, |                 NoCache = true, NoStore = true, MustRevalidate = true | ||||||
|                 NoStore = true, |  | ||||||
|                 MustRevalidate = true |  | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -604,15 +602,6 @@ public sealed partial class HttpRequestBuilder | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ContentType = RawContent switch |         ContentType = Helpers.GetContentTypeOrDefault(RawContent, defaultContentType); | ||||||
|         { |  | ||||||
|             JsonContent => MediaTypeNames.Application.Json, |  | ||||||
|             FormUrlEncodedContent => MediaTypeNames.Application.FormUrlEncoded, |  | ||||||
|             (byte[] or Stream or ByteArrayContent or StreamContent or ReadOnlyMemoryContent or ReadOnlyMemory<byte>) |  | ||||||
|                 and not StringContent => MediaTypeNames.Application |  | ||||||
|                     .Octet, |  | ||||||
|             MultipartContent => MediaTypeNames.Multipart.FormData, |  | ||||||
|             _ => defaultContentType ?? Constants.TEXT_PLAIN_MIME_TYPE |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -88,6 +88,11 @@ public sealed class HttpServerSentEventsBuilder | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     internal Type? ServerSentEventsEventHandlerType { get; private set; } |     internal Type? ServerSentEventsEventHandlerType { get; private set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     <see cref="HttpRequestBuilder" /> 配置委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<HttpRequestBuilder>? RequestConfigure { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     设置默认重新连接的间隔时间 |     ///     设置默认重新连接的间隔时间 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -220,17 +225,50 @@ public sealed class HttpServerSentEventsBuilder | |||||||
|         where TServerSentEventsEventHandler : IHttpServerSentEventsEventHandler => |         where TServerSentEventsEventHandler : IHttpServerSentEventsEventHandler => | ||||||
|         SetEventHandler(typeof(TServerSentEventsEventHandler)); |         SetEventHandler(typeof(TServerSentEventsEventHandler)); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     配置 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>支持多次调用。</remarks> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="HttpServerSentEventsBuilder" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public HttpServerSentEventsBuilder WithRequest(Action<HttpRequestBuilder> configure) | ||||||
|  |     { | ||||||
|  |         // 空检查 | ||||||
|  |         ArgumentNullException.ThrowIfNull(configure); | ||||||
|  |  | ||||||
|  |         // 如果 RequestConfigure 未设置则直接赋值 | ||||||
|  |         if (RequestConfigure is null) | ||||||
|  |         { | ||||||
|  |             RequestConfigure = configure; | ||||||
|  |         } | ||||||
|  |         // 否则创建级联调用委托 | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             // 复制一个新的委托避免死循环 | ||||||
|  |             var originalRequestConfigure = RequestConfigure; | ||||||
|  |  | ||||||
|  |             RequestConfigure = httpRequestBuilder => | ||||||
|  |             { | ||||||
|  |                 originalRequestConfigure.Invoke(httpRequestBuilder); | ||||||
|  |                 configure.Invoke(httpRequestBuilder); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     构建 <see cref="HttpRequestBuilder" /> 实例 |     ///     构建 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="httpRemoteOptions"> |     /// <param name="httpRemoteOptions"> | ||||||
|     ///     <see cref="HttpRemoteOptions" /> |     ///     <see cref="HttpRemoteOptions" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpRequestBuilder" /> |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions, Action<HttpRequestBuilder>? configure = null) |     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteOptions); |         ArgumentNullException.ThrowIfNull(httpRemoteOptions); | ||||||
| @@ -249,7 +287,7 @@ public sealed class HttpServerSentEventsBuilder | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 调用自定义配置委托 |         // 调用自定义配置委托 | ||||||
|         configure?.Invoke(httpRequestBuilder); |         RequestConfigure?.Invoke(httpRequestBuilder); | ||||||
|  |  | ||||||
|         return httpRequestBuilder; |         return httpRequestBuilder; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -59,6 +59,11 @@ public sealed class HttpStressTestHarnessBuilder | |||||||
|     /// <remarks>默认值为:1。</remarks> |     /// <remarks>默认值为:1。</remarks> | ||||||
|     public int NumberOfRounds { get; private set; } = 1; |     public int NumberOfRounds { get; private set; } = 1; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     <see cref="HttpRequestBuilder" /> 配置委托 | ||||||
|  |     /// </summary> | ||||||
|  |     internal Action<HttpRequestBuilder>? RequestConfigure { get; private set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     设置并发请求数量 |     ///     设置并发请求数量 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -124,17 +129,50 @@ public sealed class HttpStressTestHarnessBuilder | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     配置 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>支持多次调用。</remarks> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="HttpStressTestHarnessBuilder" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public HttpStressTestHarnessBuilder WithRequest(Action<HttpRequestBuilder> configure) | ||||||
|  |     { | ||||||
|  |         // 空检查 | ||||||
|  |         ArgumentNullException.ThrowIfNull(configure); | ||||||
|  |  | ||||||
|  |         // 如果 RequestConfigure 未设置则直接赋值 | ||||||
|  |         if (RequestConfigure is null) | ||||||
|  |         { | ||||||
|  |             RequestConfigure = configure; | ||||||
|  |         } | ||||||
|  |         // 否则创建级联调用委托 | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             // 复制一个新的委托避免死循环 | ||||||
|  |             var originalRequestConfigure = RequestConfigure; | ||||||
|  |  | ||||||
|  |             RequestConfigure = httpRequestBuilder => | ||||||
|  |             { | ||||||
|  |                 originalRequestConfigure.Invoke(httpRequestBuilder); | ||||||
|  |                 configure.Invoke(httpRequestBuilder); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     构建 <see cref="HttpRequestBuilder" /> 实例 |     ///     构建 <see cref="HttpRequestBuilder" /> 实例 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="httpRemoteOptions"> |     /// <param name="httpRemoteOptions"> | ||||||
|     ///     <see cref="HttpRemoteOptions" /> |     ///     <see cref="HttpRemoteOptions" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpRequestBuilder" /> |     ///     <see cref="HttpRequestBuilder" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions, Action<HttpRequestBuilder>? configure = null) |     internal HttpRequestBuilder Build(HttpRemoteOptions httpRemoteOptions) | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteOptions); |         ArgumentNullException.ThrowIfNull(httpRemoteOptions); | ||||||
| @@ -146,7 +184,7 @@ public sealed class HttpStressTestHarnessBuilder | |||||||
|             .PerformanceOptimization().UseHttpClientPool(); |             .PerformanceOptimization().UseHttpClientPool(); | ||||||
|  |  | ||||||
|         // 调用自定义配置委托 |         // 调用自定义配置委托 | ||||||
|         configure?.Invoke(httpRequestBuilder); |         RequestConfigure?.Invoke(httpRequestBuilder); | ||||||
|  |  | ||||||
|         return httpRequestBuilder; |         return httpRequestBuilder; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ namespace ThingsGateway.HttpRemote; | |||||||
| ///     HTTP 声明式多部分表单项内容特性 | ///     HTTP 声明式多部分表单项内容特性 | ||||||
| /// </summary> | /// </summary> | ||||||
| [AttributeUsage(AttributeTargets.Parameter)] | [AttributeUsage(AttributeTargets.Parameter)] | ||||||
| public sealed class MultipartAttribute : Attribute | public class MultipartAttribute : Attribute | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     <inheritdoc cref="MultipartAttribute" /> |     ///     <inheritdoc cref="MultipartAttribute" /> | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | // ------------------------------------------------------------------------ | ||||||
|  | // 版权信息 | ||||||
|  | // 版权归百小僧及百签科技(广东)有限公司所有。 | ||||||
|  | // 所有权利保留。 | ||||||
|  | // 官方网站:https://baiqian.com | ||||||
|  | // | ||||||
|  | // 许可证信息 | ||||||
|  | // 项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。 | ||||||
|  | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
|  | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | ///     HTTP 声明式多部分表单对象内容特性 | ||||||
|  | /// </summary> | ||||||
|  | [AttributeUsage(AttributeTargets.Parameter)] | ||||||
|  | public sealed class MultipartObjectAttribute : MultipartAttribute | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     ///     <inheritdoc cref="MultipartObjectAttribute" /> | ||||||
|  |     /// </summary> | ||||||
|  |     public MultipartObjectAttribute() => AsFormItem = false; | ||||||
|  | } | ||||||
| @@ -11,11 +11,10 @@ | |||||||
|  |  | ||||||
| // ReSharper disable ArrangeObjectCreationWhenTypeNotEvident | // ReSharper disable ArrangeObjectCreationWhenTypeNotEvident | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
|   | |||||||
| @@ -9,11 +9,10 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using System.Net.Mime; | using System.Net.Mime; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
|   | |||||||
| @@ -9,10 +9,9 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using System.Reflection; |  | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extensions; | ||||||
| using ThingsGateway.Utilities; | using ThingsGateway.Utilities; | ||||||
|  | using System.Reflection; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,10 +9,9 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using System.Reflection; |  | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extensions; | ||||||
| using ThingsGateway.Utilities; | using ThingsGateway.Utilities; | ||||||
|  | using System.Reflection; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,13 +9,12 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using System.Globalization; | using System.Globalization; | ||||||
| using System.Net.Mime; | using System.Net.Mime; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
|   | |||||||
| @@ -9,10 +9,9 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using System.Reflection; |  | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extensions; | ||||||
| using ThingsGateway.Utilities; | using ThingsGateway.Utilities; | ||||||
|  | using System.Reflection; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,10 +9,9 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using System.Reflection; |  | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; | using ThingsGateway.Extensions; | ||||||
| using ThingsGateway.Utilities; | using ThingsGateway.Utilities; | ||||||
|  | using System.Reflection; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,11 +9,10 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
|   | |||||||
| @@ -9,11 +9,10 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using System.Diagnostics.CodeAnalysis; | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
|   | |||||||
| @@ -9,13 +9,11 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
|  |  | ||||||
| using System.Net.Http.Headers; | using System.Net.Http.Headers; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote.Extensions; | namespace ThingsGateway.HttpRemote.Extensions; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ | |||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
|  |  | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|   | |||||||
| @@ -9,18 +9,15 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
|  | using ThingsGateway.Utilities; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.DependencyInjection.Extensions; | using Microsoft.Extensions.DependencyInjection.Extensions; | ||||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||||
|  |  | ||||||
| using System.Diagnostics.CodeAnalysis; | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Net.Http.Headers; | using System.Net.Http.Headers; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
| using ThingsGateway.Utilities; |  | ||||||
|  |  | ||||||
| using StringWithQualityHeaderValue = System.Net.Http.Headers.StringWithQualityHeaderValue; | using StringWithQualityHeaderValue = System.Net.Http.Headers.StringWithQualityHeaderValue; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote.Extensions; | namespace ThingsGateway.HttpRemote.Extensions; | ||||||
| @@ -286,6 +283,11 @@ public static partial class HttpRemoteExtensions | |||||||
|         var charset = httpContent.Headers.ContentType?.CharSet ?? "utf-8"; |         var charset = httpContent.Headers.ContentType?.CharSet ?? "utf-8"; | ||||||
|         var partialContent = Encoding.GetEncoding(charset).GetString(buffer, 0, bytesToShow); |         var partialContent = Encoding.GetEncoding(charset).GetString(buffer, 0, bytesToShow); | ||||||
|  |  | ||||||
|  |         // 解决退格导致显示不全问题:保留 \n 和 \r,仅过滤其他 ASCII 控制字符(ASCII < 32 且不是 \n 或 \r) | ||||||
|  |         partialContent = new string(partialContent | ||||||
|  |             .Where(c => c >= 32 || c == '\n' || c == '\r') | ||||||
|  |             .ToArray()); | ||||||
|  |  | ||||||
|         // 检查是否是完整的 Unicode 转义字符串 |         // 检查是否是完整的 Unicode 转义字符串 | ||||||
|         if (total == bytesToShow && UnicodeEscapeRegex().IsMatch(partialContent)) |         if (total == bytesToShow && UnicodeEscapeRegex().IsMatch(partialContent)) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -9,14 +9,14 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Utilities; | ||||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||||
|  |  | ||||||
| using System.Diagnostics.CodeAnalysis; | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Net; | using System.Net; | ||||||
|  | using System.Net.Http.Json; | ||||||
|  | using System.Net.Mime; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| using ThingsGateway.Utilities; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -213,6 +213,27 @@ internal static partial class Helpers | |||||||
|             $"{requestUri.Scheme}://{requestUri.Host}{(requestUri.IsDefaultPort ? string.Empty : $":{requestUri.Port}")}"); |             $"{requestUri.Scheme}://{requestUri.Host}{(requestUri.IsDefaultPort ? string.Empty : $":{requestUri.Port}")}"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     根据原始内容推断内容类型,失败时返回默认值 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="rawContent">原始请求内容</param> | ||||||
|  |     /// <param name="defaultContentType">默认请求内容类型</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="string" /> | ||||||
|  |     /// </returns> | ||||||
|  |     internal static string GetContentTypeOrDefault(object? rawContent, string? defaultContentType) => | ||||||
|  |         rawContent switch | ||||||
|  |         { | ||||||
|  |             JsonContent => MediaTypeNames.Application.Json, | ||||||
|  |             FormUrlEncodedContent => MediaTypeNames.Application.FormUrlEncoded, | ||||||
|  |             (byte[] or Stream or ByteArrayContent or StreamContent or ReadOnlyMemoryContent or ReadOnlyMemory<byte>) | ||||||
|  |                 and not StringContent => MediaTypeNames.Application | ||||||
|  |                     .Octet, | ||||||
|  |             MultipartContent => MediaTypeNames.Multipart.FormData, | ||||||
|  |             MultipartFile => MediaTypeNames.Application.Octet, | ||||||
|  |             _ => defaultContentType ?? Constants.TEXT_PLAIN_MIME_TYPE | ||||||
|  |         }; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     <c>application/x-www-form-urlencoded</c> 格式正则表达式 |     ///     <c>application/x-www-form-urlencoded</c> 格式正则表达式 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
| @@ -9,14 +9,12 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||||
|  |  | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Threading.Channels; | using System.Threading.Channels; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -44,9 +42,7 @@ internal sealed class FileDownloadManager | |||||||
|     /// <param name="httpFileDownloadBuilder"> |     /// <param name="httpFileDownloadBuilder"> | ||||||
|     ///     <see cref="HttpFileDownloadBuilder" /> |     ///     <see cref="HttpFileDownloadBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     internal FileDownloadManager(IHttpRemoteService httpRemoteService, HttpFileDownloadBuilder httpFileDownloadBuilder) | ||||||
|     internal FileDownloadManager(IHttpRemoteService httpRemoteService, HttpFileDownloadBuilder httpFileDownloadBuilder, |  | ||||||
|         Action<HttpRequestBuilder>? configure = null) |  | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteService); |         ArgumentNullException.ThrowIfNull(httpRemoteService); | ||||||
| @@ -65,7 +61,7 @@ internal sealed class FileDownloadManager | |||||||
|  |  | ||||||
|         // 构建 HttpRequestBuilder 实例 |         // 构建 HttpRequestBuilder 实例 | ||||||
|         RequestBuilder = httpFileDownloadBuilder.Build(httpRemoteService.ServiceProvider |         RequestBuilder = httpFileDownloadBuilder.Build(httpRemoteService.ServiceProvider | ||||||
|             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value, configure); |             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -9,14 +9,12 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||||
|  |  | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Threading.Channels; | using System.Threading.Channels; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -44,9 +42,7 @@ internal sealed class FileUploadManager | |||||||
|     /// <param name="httpFileUploadBuilder"> |     /// <param name="httpFileUploadBuilder"> | ||||||
|     ///     <see cref="HttpFileUploadBuilder" /> |     ///     <see cref="HttpFileUploadBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     internal FileUploadManager(IHttpRemoteService httpRemoteService, HttpFileUploadBuilder httpFileUploadBuilder) | ||||||
|     internal FileUploadManager(IHttpRemoteService httpRemoteService, HttpFileUploadBuilder httpFileUploadBuilder, |  | ||||||
|         Action<HttpRequestBuilder>? configure = null) |  | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteService); |         ArgumentNullException.ThrowIfNull(httpRemoteService); | ||||||
| @@ -65,7 +61,7 @@ internal sealed class FileUploadManager | |||||||
|  |  | ||||||
|         // 构建 HttpRequestBuilder 实例 |         // 构建 HttpRequestBuilder 实例 | ||||||
|         RequestBuilder = httpFileUploadBuilder.Build(httpRemoteService.ServiceProvider |         RequestBuilder = httpFileUploadBuilder.Build(httpRemoteService.ServiceProvider | ||||||
|             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value, _progressChannel, configure); |             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value, _progressChannel); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -9,13 +9,11 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||||
|  |  | ||||||
| using System.Threading.Channels; | using System.Threading.Channels; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -38,9 +36,7 @@ internal sealed class LongPollingManager | |||||||
|     /// <param name="httpLongPollingBuilder"> |     /// <param name="httpLongPollingBuilder"> | ||||||
|     ///     <see cref="HttpLongPollingBuilder" /> |     ///     <see cref="HttpLongPollingBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     internal LongPollingManager(IHttpRemoteService httpRemoteService, HttpLongPollingBuilder httpLongPollingBuilder) | ||||||
|     internal LongPollingManager(IHttpRemoteService httpRemoteService, HttpLongPollingBuilder httpLongPollingBuilder, |  | ||||||
|         Action<HttpRequestBuilder>? configure = null) |  | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteService); |         ArgumentNullException.ThrowIfNull(httpRemoteService); | ||||||
| @@ -56,7 +52,7 @@ internal sealed class LongPollingManager | |||||||
|  |  | ||||||
|         // 构建 HttpRequestBuilder 实例 |         // 构建 HttpRequestBuilder 实例 | ||||||
|         RequestBuilder = httpLongPollingBuilder.Build(httpRemoteService.ServiceProvider |         RequestBuilder = httpLongPollingBuilder.Build(httpRemoteService.ServiceProvider | ||||||
|             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value, configure); |             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -9,15 +9,13 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||||
|  |  | ||||||
| using System.Diagnostics.CodeAnalysis; | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Channels; | using System.Threading.Channels; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -41,10 +39,8 @@ internal sealed class ServerSentEventsManager | |||||||
|     /// <param name="httpServerSentEventsBuilder"> |     /// <param name="httpServerSentEventsBuilder"> | ||||||
|     ///     <see cref="HttpServerSentEventsBuilder" /> |     ///     <see cref="HttpServerSentEventsBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     internal ServerSentEventsManager(IHttpRemoteService httpRemoteService, |     internal ServerSentEventsManager(IHttpRemoteService httpRemoteService, | ||||||
|         HttpServerSentEventsBuilder httpServerSentEventsBuilder, |         HttpServerSentEventsBuilder httpServerSentEventsBuilder) | ||||||
|         Action<HttpRequestBuilder>? configure = null) |  | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteService); |         ArgumentNullException.ThrowIfNull(httpRemoteService); | ||||||
| @@ -62,7 +58,7 @@ internal sealed class ServerSentEventsManager | |||||||
|  |  | ||||||
|         // 构建 HttpRequestBuilder 实例 |         // 构建 HttpRequestBuilder 实例 | ||||||
|         RequestBuilder = httpServerSentEventsBuilder.Build(httpRemoteService.ServiceProvider |         RequestBuilder = httpServerSentEventsBuilder.Build(httpRemoteService.ServiceProvider | ||||||
|             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value, configure); |             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -222,7 +218,7 @@ internal sealed class ServerSentEventsManager | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             // 获取 HTTP 响应体中的内容流 |             // 获取 HTTP 响应体中的内容流 | ||||||
|             using var contentStream = (await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)); |              using var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|             // 初始化 StreamReader 实例 |             // 初始化 StreamReader 实例 | ||||||
|             using var streamReader = new StreamReader(contentStream, Encoding.UTF8); |             using var streamReader = new StreamReader(contentStream, Encoding.UTF8); | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||||
|  |  | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
| @@ -36,10 +35,8 @@ internal sealed class StressTestHarnessManager | |||||||
|     /// <param name="httpStressTestHarnessBuilder"> |     /// <param name="httpStressTestHarnessBuilder"> | ||||||
|     ///     <see cref="HttpStressTestHarnessBuilder" /> |     ///     <see cref="HttpStressTestHarnessBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     internal StressTestHarnessManager(IHttpRemoteService httpRemoteService, |     internal StressTestHarnessManager(IHttpRemoteService httpRemoteService, | ||||||
|         HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, |         HttpStressTestHarnessBuilder httpStressTestHarnessBuilder) | ||||||
|         Action<HttpRequestBuilder>? configure = null) |  | ||||||
|     { |     { | ||||||
|         // 空检查 |         // 空检查 | ||||||
|         ArgumentNullException.ThrowIfNull(httpRemoteService); |         ArgumentNullException.ThrowIfNull(httpRemoteService); | ||||||
| @@ -50,7 +47,7 @@ internal sealed class StressTestHarnessManager | |||||||
|  |  | ||||||
|         // 构建 HttpRequestBuilder 实例 |         // 构建 HttpRequestBuilder 实例 | ||||||
|         RequestBuilder = httpStressTestHarnessBuilder.Build(httpRemoteService.ServiceProvider |         RequestBuilder = httpStressTestHarnessBuilder.Build(httpRemoteService.ServiceProvider | ||||||
|             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value, configure); |             .GetRequiredService<IOptions<HttpRemoteOptions>>().Value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -22,141 +22,121 @@ internal sealed partial class HttpRemoteService | |||||||
|     public void DownloadFile(string? requestUri, string? destinationPath, |     public void DownloadFile(string? requestUri, string? destinationPath, | ||||||
|         Func<FileTransferProgress, Task>? onProgressChanged = null, |         Func<FileTransferProgress, Task>? onProgressChanged = null, | ||||||
|         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, |         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, | ||||||
|         Action<HttpFileDownloadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileDownloadBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |  | ||||||
|         Send( |         Send( | ||||||
|             HttpRequestBuilder.DownloadFile(requestUri, destinationPath, onProgressChanged, fileExistsBehavior, |             HttpRequestBuilder.DownloadFile(requestUri, destinationPath, onProgressChanged, fileExistsBehavior, | ||||||
|                 configure), |                 configure), cancellationToken); | ||||||
|             requestConfigure, cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task DownloadFileAsync(string? requestUri, string? destinationPath, |     public Task DownloadFileAsync(string? requestUri, string? destinationPath, | ||||||
|         Func<FileTransferProgress, Task>? onProgressChanged = null, |         Func<FileTransferProgress, Task>? onProgressChanged = null, | ||||||
|         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, |         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, | ||||||
|         Action<HttpFileDownloadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileDownloadBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |  | ||||||
|         SendAsync( |         SendAsync( | ||||||
|             HttpRequestBuilder.DownloadFile(requestUri, destinationPath, onProgressChanged, fileExistsBehavior, |             HttpRequestBuilder.DownloadFile(requestUri, destinationPath, onProgressChanged, fileExistsBehavior, | ||||||
|                 configure), |                 configure), cancellationToken); | ||||||
|             requestConfigure, cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public void Send(HttpFileDownloadBuilder httpFileDownloadBuilder, Action<HttpRequestBuilder>? configure = null, |     public void Send(HttpFileDownloadBuilder httpFileDownloadBuilder, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |         new FileDownloadManager(this, httpFileDownloadBuilder).Start(cancellationToken); | ||||||
|         new FileDownloadManager(this, httpFileDownloadBuilder, configure).Start(cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task SendAsync(HttpFileDownloadBuilder httpFileDownloadBuilder, Action<HttpRequestBuilder>? configure = null, |     public Task SendAsync(HttpFileDownloadBuilder httpFileDownloadBuilder, | ||||||
|         CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new FileDownloadManager(this, httpFileDownloadBuilder, configure).StartAsync(cancellationToken); |         new FileDownloadManager(this, httpFileDownloadBuilder).StartAsync(cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <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, |         Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, | ||||||
|         Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileUploadBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |  | ||||||
|         Send(HttpRequestBuilder.UploadFile(requestUri, filePath, name, onProgressChanged, fileName, configure), |         Send(HttpRequestBuilder.UploadFile(requestUri, filePath, name, onProgressChanged, fileName, configure), | ||||||
|             requestConfigure, |  | ||||||
|             cancellationToken); |             cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <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, |         Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, | ||||||
|         Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileUploadBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |  | ||||||
|         SendAsync(HttpRequestBuilder.UploadFile(requestUri, filePath, name, onProgressChanged, fileName, configure), |         SendAsync(HttpRequestBuilder.UploadFile(requestUri, filePath, name, onProgressChanged, fileName, configure), | ||||||
|             requestConfigure, |  | ||||||
|             cancellationToken); |             cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder, |     public HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new FileUploadManager(this, httpFileUploadBuilder, configure).Start(cancellationToken); |         new FileUploadManager(this, httpFileUploadBuilder).Start(cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder, |     public Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new FileUploadManager(this, httpFileUploadBuilder, configure).StartAsync(cancellationToken); |         new FileUploadManager(this, httpFileUploadBuilder).StartAsync(cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public void ServerSentEvents(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, |     public void ServerSentEvents(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, | ||||||
|         Action<HttpServerSentEventsBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpServerSentEventsBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |         Send(HttpRequestBuilder.ServerSentEvents(requestUri, onMessage, configure), cancellationToken); | ||||||
|         Send(HttpRequestBuilder.ServerSentEvents(requestUri, onMessage, configure), requestConfigure, |  | ||||||
|             cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task ServerSentEventsAsync(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, |     public Task ServerSentEventsAsync(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, | ||||||
|         Action<HttpServerSentEventsBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpServerSentEventsBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |         SendAsync(HttpRequestBuilder.ServerSentEvents(requestUri, onMessage, configure), cancellationToken); | ||||||
|         SendAsync(HttpRequestBuilder.ServerSentEvents(requestUri, onMessage, configure), requestConfigure, |  | ||||||
|             cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public void Send(HttpServerSentEventsBuilder httpServerSentEventsBuilder, |     public void Send(HttpServerSentEventsBuilder httpServerSentEventsBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new ServerSentEventsManager(this, httpServerSentEventsBuilder, configure).Start(cancellationToken); |         new ServerSentEventsManager(this, httpServerSentEventsBuilder).Start(cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task SendAsync(HttpServerSentEventsBuilder httpServerSentEventsBuilder, |     public Task SendAsync(HttpServerSentEventsBuilder httpServerSentEventsBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new ServerSentEventsManager(this, httpServerSentEventsBuilder, configure).StartAsync(cancellationToken); |         new ServerSentEventsManager(this, httpServerSentEventsBuilder).StartAsync(cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public StressTestHarnessResult StressTestHarness(string? requestUri, int numberOfRequests = 100, |     public StressTestHarnessResult StressTestHarness(string? requestUri, int numberOfRequests = 100, | ||||||
|         Action<HttpStressTestHarnessBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpStressTestHarnessBuilder>? configure = null, | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         Send(HttpRequestBuilder.StressTestHarness(requestUri, numberOfRequests, configure), requestConfigure, |         Send(HttpRequestBuilder.StressTestHarness(requestUri, numberOfRequests, configure), completionOption, | ||||||
|             completionOption, cancellationToken); |             cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task<StressTestHarnessResult> StressTestHarnessAsync(string? requestUri, int numberOfRequests = 100, |     public Task<StressTestHarnessResult> StressTestHarnessAsync(string? requestUri, int numberOfRequests = 100, | ||||||
|         Action<HttpStressTestHarnessBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpStressTestHarnessBuilder>? configure = null, | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         SendAsync(HttpRequestBuilder.StressTestHarness(requestUri, numberOfRequests, configure), requestConfigure, |         SendAsync(HttpRequestBuilder.StressTestHarness(requestUri, numberOfRequests, configure), completionOption, | ||||||
|             completionOption, cancellationToken); |             cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public StressTestHarnessResult Send(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, |     public StressTestHarnessResult Send(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, |  | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new StressTestHarnessManager(this, httpStressTestHarnessBuilder, configure).Start(completionOption, |         new StressTestHarnessManager(this, httpStressTestHarnessBuilder).Start(completionOption, | ||||||
|             cancellationToken); |             cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task<StressTestHarnessResult> SendAsync(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, |     public Task<StressTestHarnessResult> SendAsync(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, |  | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new StressTestHarnessManager(this, httpStressTestHarnessBuilder, configure).StartAsync(completionOption, |         new StressTestHarnessManager(this, httpStressTestHarnessBuilder).StartAsync(completionOption, | ||||||
|             cancellationToken); |             cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public void LongPolling(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, |     public void LongPolling(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, | ||||||
|         Action<HttpLongPollingBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpLongPollingBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |         Send(HttpRequestBuilder.LongPolling(requestUri, onDataReceived, configure), cancellationToken); | ||||||
|         Send(HttpRequestBuilder.LongPolling(requestUri, onDataReceived, configure), requestConfigure, |  | ||||||
|             cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task LongPollingAsync(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, |     public Task LongPollingAsync(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, | ||||||
|         Action<HttpLongPollingBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpLongPollingBuilder>? configure = null, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |         SendAsync(HttpRequestBuilder.LongPolling(requestUri, onDataReceived, configure), cancellationToken); | ||||||
|         SendAsync(HttpRequestBuilder.LongPolling(requestUri, onDataReceived, configure), requestConfigure, |  | ||||||
|             cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public void Send(HttpLongPollingBuilder httpLongPollingBuilder, Action<HttpRequestBuilder>? configure = null, |     public void Send(HttpLongPollingBuilder httpLongPollingBuilder, CancellationToken cancellationToken = default) => | ||||||
|         CancellationToken cancellationToken = default) => |         new LongPollingManager(this, httpLongPollingBuilder).Start(cancellationToken); | ||||||
|         new LongPollingManager(this, httpLongPollingBuilder, configure).Start(cancellationToken); |  | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public Task SendAsync(HttpLongPollingBuilder httpLongPollingBuilder, Action<HttpRequestBuilder>? configure = null, |     public Task SendAsync(HttpLongPollingBuilder httpLongPollingBuilder, | ||||||
|         CancellationToken cancellationToken = default) => |         CancellationToken cancellationToken = default) => | ||||||
|         new LongPollingManager(this, httpLongPollingBuilder, configure).StartAsync(cancellationToken); |         new LongPollingManager(this, httpLongPollingBuilder).StartAsync(cancellationToken); | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     public object? Declarative(MethodInfo method, object[] args) => |     public object? Declarative(MethodInfo method, object[] args) => | ||||||
|   | |||||||
| @@ -9,17 +9,16 @@ | |||||||
| // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | // 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。 | ||||||
| // ------------------------------------------------------------------------ | // ------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.Extensions; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||||
|  |  | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Net; | using System.Net; | ||||||
|  | using System.Net.Http.Headers; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| using ThingsGateway.Extensions; |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.HttpRemote; | namespace ThingsGateway.HttpRemote; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -346,8 +345,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService | |||||||
|         // 初始化 HttpRemoteResult 实例 |         // 初始化 HttpRemoteResult 实例 | ||||||
|         var httpRemoteResult = new HttpRemoteResult<TResult>(httpResponseMessage) |         var httpRemoteResult = new HttpRemoteResult<TResult>(httpResponseMessage) | ||||||
|         { |         { | ||||||
|             Result = result, |             Result = result, RequestDuration = requestDuration | ||||||
|             RequestDuration = requestDuration |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         return httpRemoteResult; |         return httpRemoteResult; | ||||||
| @@ -380,8 +378,7 @@ internal sealed partial class HttpRemoteService : IHttpRemoteService | |||||||
|         // 初始化 HttpRemoteResult 实例 |         // 初始化 HttpRemoteResult 实例 | ||||||
|         var httpRemoteResult = new HttpRemoteResult<TResult>(httpResponseMessage) |         var httpRemoteResult = new HttpRemoteResult<TResult>(httpResponseMessage) | ||||||
|         { |         { | ||||||
|             Result = result, |             Result = result, RequestDuration = requestDuration | ||||||
|             RequestDuration = requestDuration |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         return httpRemoteResult; |         return httpRemoteResult; | ||||||
|   | |||||||
| @@ -28,15 +28,13 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="FileExistsBehavior" /> |     ///     <see cref="FileExistsBehavior" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     void DownloadFile(string? requestUri, string? destinationPath, |     void DownloadFile(string? requestUri, string? destinationPath, | ||||||
|         Func<FileTransferProgress, Task>? onProgressChanged = null, |         Func<FileTransferProgress, Task>? onProgressChanged = null, | ||||||
|         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, |         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, | ||||||
|         Action<HttpFileDownloadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileDownloadBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     下载文件 |     ///     下载文件 | ||||||
| @@ -48,7 +46,6 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="FileExistsBehavior" /> |     ///     <see cref="FileExistsBehavior" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -58,8 +55,7 @@ public partial interface IHttpRemoteService | |||||||
|     Task DownloadFileAsync(string? requestUri, string? destinationPath, |     Task DownloadFileAsync(string? requestUri, string? destinationPath, | ||||||
|         Func<FileTransferProgress, Task>? onProgressChanged = null, |         Func<FileTransferProgress, Task>? onProgressChanged = null, | ||||||
|         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, |         FileExistsBehavior fileExistsBehavior = FileExistsBehavior.CreateNew, | ||||||
|         Action<HttpFileDownloadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileDownloadBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     下载文件 |     ///     下载文件 | ||||||
| @@ -67,12 +63,10 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpFileDownloadBuilder"> |     /// <param name="httpFileDownloadBuilder"> | ||||||
|     ///     <see cref="HttpFileDownloadBuilder" /> |     ///     <see cref="HttpFileDownloadBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     void Send(HttpFileDownloadBuilder httpFileDownloadBuilder, Action<HttpRequestBuilder>? configure = null, |     void Send(HttpFileDownloadBuilder httpFileDownloadBuilder, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     下载文件 |     ///     下载文件 | ||||||
| @@ -80,15 +74,13 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpFileDownloadBuilder"> |     /// <param name="httpFileDownloadBuilder"> | ||||||
|     ///     <see cref="HttpFileDownloadBuilder" /> |     ///     <see cref="HttpFileDownloadBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="Task" /> |     ///     <see cref="Task" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task SendAsync(HttpFileDownloadBuilder httpFileDownloadBuilder, Action<HttpRequestBuilder>? configure = null, |     Task SendAsync(HttpFileDownloadBuilder httpFileDownloadBuilder, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     上传文件 |     ///     上传文件 | ||||||
| @@ -99,7 +91,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="onProgressChanged">用于传输进度发生变化时执行的委托</param> |     /// <param name="onProgressChanged">用于传输进度发生变化时执行的委托</param> | ||||||
|     /// <param name="fileName">文件的名称</param> |     /// <param name="fileName">文件的名称</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -108,8 +99,7 @@ public partial interface IHttpRemoteService | |||||||
|     /// </returns> |     /// </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, |         Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, | ||||||
|         Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileUploadBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     上传文件 |     ///     上传文件 | ||||||
| @@ -120,7 +110,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="onProgressChanged">用于传输进度发生变化时执行的委托</param> |     /// <param name="onProgressChanged">用于传输进度发生变化时执行的委托</param> | ||||||
|     /// <param name="fileName">文件的名称</param> |     /// <param name="fileName">文件的名称</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -129,8 +118,7 @@ public partial interface IHttpRemoteService | |||||||
|     /// </returns> |     /// </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, |         Func<FileTransferProgress, Task>? onProgressChanged = null, string? fileName = null, | ||||||
|         Action<HttpFileUploadBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpFileUploadBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     上传文件 |     ///     上传文件 | ||||||
| @@ -138,14 +126,13 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpFileUploadBuilder"> |     /// <param name="httpFileUploadBuilder"> | ||||||
|     ///     <see cref="HttpFileUploadBuilder" /> |     ///     <see cref="HttpFileUploadBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="HttpResponseMessage" /> |     ///     <see cref="HttpResponseMessage" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder, Action<HttpRequestBuilder>? configure = null, |     HttpResponseMessage? Send(HttpFileUploadBuilder httpFileUploadBuilder, | ||||||
|         CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -154,7 +141,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpFileUploadBuilder"> |     /// <param name="httpFileUploadBuilder"> | ||||||
|     ///     <see cref="HttpFileUploadBuilder" /> |     ///     <see cref="HttpFileUploadBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -162,20 +148,6 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="Task{TResult}" /> |     ///     <see cref="Task{TResult}" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder, |     Task<HttpResponseMessage?> SendAsync(HttpFileUploadBuilder httpFileUploadBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     ///     发送 Server-Sent Events 请求 |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="requestUri">请求地址</param> |  | ||||||
|     /// <param name="onMessage">用于在从事件源接收到数据时的操作</param> |  | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |  | ||||||
|     ///     <see cref="CancellationToken" /> |  | ||||||
|     /// </param> |  | ||||||
|     void ServerSentEvents(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, |  | ||||||
|         Action<HttpServerSentEventsBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |  | ||||||
|         CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -184,7 +156,18 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="requestUri">请求地址</param> |     /// <param name="requestUri">请求地址</param> | ||||||
|     /// <param name="onMessage">用于在从事件源接收到数据时的操作</param> |     /// <param name="onMessage">用于在从事件源接收到数据时的操作</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |     /// <param name="cancellationToken"> | ||||||
|  |     ///     <see cref="CancellationToken" /> | ||||||
|  |     /// </param> | ||||||
|  |     void ServerSentEvents(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, | ||||||
|  |         Action<HttpServerSentEventsBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     发送 Server-Sent Events 请求 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="requestUri">请求地址</param> | ||||||
|  |     /// <param name="onMessage">用于在从事件源接收到数据时的操作</param> | ||||||
|  |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -192,8 +175,7 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="Task" /> |     ///     <see cref="Task" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task ServerSentEventsAsync(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, |     Task ServerSentEventsAsync(string? requestUri, Func<ServerSentEventsData, CancellationToken, Task> onMessage, | ||||||
|         Action<HttpServerSentEventsBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpServerSentEventsBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     发送 Server-Sent Events 请求 |     ///     发送 Server-Sent Events 请求 | ||||||
| @@ -201,12 +183,10 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpServerSentEventsBuilder"> |     /// <param name="httpServerSentEventsBuilder"> | ||||||
|     ///     <see cref="HttpServerSentEventsBuilder" /> |     ///     <see cref="HttpServerSentEventsBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     void Send(HttpServerSentEventsBuilder httpServerSentEventsBuilder, Action<HttpRequestBuilder>? configure = null, |     void Send(HttpServerSentEventsBuilder httpServerSentEventsBuilder, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     发送 Server-Sent Events 请求 |     ///     发送 Server-Sent Events 请求 | ||||||
| @@ -214,7 +194,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpServerSentEventsBuilder"> |     /// <param name="httpServerSentEventsBuilder"> | ||||||
|     ///     <see cref="HttpServerSentEventsBuilder" /> |     ///     <see cref="HttpServerSentEventsBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -222,7 +201,7 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="Task" /> |     ///     <see cref="Task" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task SendAsync(HttpServerSentEventsBuilder httpServerSentEventsBuilder, |     Task SendAsync(HttpServerSentEventsBuilder httpServerSentEventsBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     压力测试 |     ///     压力测试 | ||||||
| @@ -230,7 +209,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="requestUri">请求地址</param> |     /// <param name="requestUri">请求地址</param> | ||||||
|     /// <param name="numberOfRequests">并发请求数量,默认值为:100。</param> |     /// <param name="numberOfRequests">并发请求数量,默认值为:100。</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="completionOption"> |     /// <param name="completionOption"> | ||||||
|     ///     <see cref="HttpCompletionOption" /> |     ///     <see cref="HttpCompletionOption" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -241,7 +219,7 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="StressTestHarnessResult" /> |     ///     <see cref="StressTestHarnessResult" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     StressTestHarnessResult StressTestHarness(string? requestUri, int numberOfRequests = 100, |     StressTestHarnessResult StressTestHarness(string? requestUri, int numberOfRequests = 100, | ||||||
|         Action<HttpStressTestHarnessBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpStressTestHarnessBuilder>? configure = null, | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
| @@ -251,7 +229,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="requestUri">请求地址</param> |     /// <param name="requestUri">请求地址</param> | ||||||
|     /// <param name="numberOfRequests">并发请求数量,默认值为:100。</param> |     /// <param name="numberOfRequests">并发请求数量,默认值为:100。</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="completionOption"> |     /// <param name="completionOption"> | ||||||
|     ///     <see cref="HttpCompletionOption" /> |     ///     <see cref="HttpCompletionOption" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -262,7 +239,7 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="Task{TResult}" /> |     ///     <see cref="Task{TResult}" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task<StressTestHarnessResult> StressTestHarnessAsync(string? requestUri, int numberOfRequests = 100, |     Task<StressTestHarnessResult> StressTestHarnessAsync(string? requestUri, int numberOfRequests = 100, | ||||||
|         Action<HttpStressTestHarnessBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpStressTestHarnessBuilder>? configure = null, | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
| @@ -272,7 +249,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpStressTestHarnessBuilder"> |     /// <param name="httpStressTestHarnessBuilder"> | ||||||
|     ///     <see cref="HttpStressTestHarnessBuilder" /> |     ///     <see cref="HttpStressTestHarnessBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="completionOption"> |     /// <param name="completionOption"> | ||||||
|     ///     <see cref="HttpCompletionOption" /> |     ///     <see cref="HttpCompletionOption" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -283,7 +259,6 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="StressTestHarnessResult" /> |     ///     <see cref="StressTestHarnessResult" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     StressTestHarnessResult Send(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, |     StressTestHarnessResult Send(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, |  | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
| @@ -293,7 +268,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpStressTestHarnessBuilder"> |     /// <param name="httpStressTestHarnessBuilder"> | ||||||
|     ///     <see cref="HttpStressTestHarnessBuilder" /> |     ///     <see cref="HttpStressTestHarnessBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="completionOption"> |     /// <param name="completionOption"> | ||||||
|     ///     <see cref="HttpCompletionOption" /> |     ///     <see cref="HttpCompletionOption" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -304,7 +278,6 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="Task{TResult}" /> |     ///     <see cref="Task{TResult}" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task<StressTestHarnessResult> SendAsync(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, |     Task<StressTestHarnessResult> SendAsync(HttpStressTestHarnessBuilder httpStressTestHarnessBuilder, | ||||||
|         Action<HttpRequestBuilder>? configure = null, |  | ||||||
|         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, |         HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, | ||||||
|         CancellationToken cancellationToken = default); |         CancellationToken cancellationToken = default); | ||||||
|  |  | ||||||
| @@ -314,13 +287,11 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="requestUri">请求地址</param> |     /// <param name="requestUri">请求地址</param> | ||||||
|     /// <param name="onDataReceived">用于接收服务器返回 <c>200~299</c> 状态码的数据的操作</param> |     /// <param name="onDataReceived">用于接收服务器返回 <c>200~299</c> 状态码的数据的操作</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     void LongPolling(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, |     void LongPolling(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, | ||||||
|         Action<HttpLongPollingBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpLongPollingBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     发送长轮询请求 |     ///     发送长轮询请求 | ||||||
| @@ -328,7 +299,6 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="requestUri">请求地址</param> |     /// <param name="requestUri">请求地址</param> | ||||||
|     /// <param name="onDataReceived">用于接收服务器返回 <c>200~299</c> 状态码的数据的操作</param> |     /// <param name="onDataReceived">用于接收服务器返回 <c>200~299</c> 状态码的数据的操作</param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |     /// <param name="configure">自定义配置委托</param> | ||||||
|     /// <param name="requestConfigure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
| @@ -336,8 +306,7 @@ public partial interface IHttpRemoteService | |||||||
|     ///     <see cref="Task" /> |     ///     <see cref="Task" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task LongPollingAsync(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, |     Task LongPollingAsync(string? requestUri, Func<HttpResponseMessage, CancellationToken, Task> onDataReceived, | ||||||
|         Action<HttpLongPollingBuilder>? configure = null, Action<HttpRequestBuilder>? requestConfigure = null, |         Action<HttpLongPollingBuilder>? configure = null, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     发送长轮询请求 |     ///     发送长轮询请求 | ||||||
| @@ -345,12 +314,10 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpLongPollingBuilder"> |     /// <param name="httpLongPollingBuilder"> | ||||||
|     ///     <see cref="HttpLongPollingBuilder" /> |     ///     <see cref="HttpLongPollingBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     void Send(HttpLongPollingBuilder httpLongPollingBuilder, Action<HttpRequestBuilder>? configure = null, |     void Send(HttpLongPollingBuilder httpLongPollingBuilder, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     发送长轮询请求 |     ///     发送长轮询请求 | ||||||
| @@ -358,15 +325,13 @@ public partial interface IHttpRemoteService | |||||||
|     /// <param name="httpLongPollingBuilder"> |     /// <param name="httpLongPollingBuilder"> | ||||||
|     ///     <see cref="HttpLongPollingBuilder" /> |     ///     <see cref="HttpLongPollingBuilder" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <param name="configure">自定义配置委托</param> |  | ||||||
|     /// <param name="cancellationToken"> |     /// <param name="cancellationToken"> | ||||||
|     ///     <see cref="CancellationToken" /> |     ///     <see cref="CancellationToken" /> | ||||||
|     /// </param> |     /// </param> | ||||||
|     /// <returns> |     /// <returns> | ||||||
|     ///     <see cref="Task" /> |     ///     <see cref="Task" /> | ||||||
|     /// </returns> |     /// </returns> | ||||||
|     Task SendAsync(HttpLongPollingBuilder httpLongPollingBuilder, Action<HttpRequestBuilder>? configure = null, |     Task SendAsync(HttpLongPollingBuilder httpLongPollingBuilder, CancellationToken cancellationToken = default); | ||||||
|         CancellationToken cancellationToken = default); |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     发送 HTTP 声明式请求 |     ///     发送 HTTP 声明式请求 | ||||||
|   | |||||||
| @@ -556,7 +556,7 @@ public partial class Clay | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             // 进行下一级查找 |             // 进行下一级查找 | ||||||
|             currentNode = ((Clay?)currentValue)?.FindNode(identifiers[i]); |             currentNode = ((Clay)currentValue).FindNode(identifiers[i]); | ||||||
|             if (currentNode is null) |             if (currentNode is null) | ||||||
|             { |             { | ||||||
|                 return null; |                 return null; | ||||||
| @@ -934,6 +934,67 @@ public partial class Clay | |||||||
|         return Remove(new Range(start, end)); |         return Remove(new Range(start, end)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     根据标识符(路径)删除数据 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="identifier">带路径的标识符</param> | ||||||
|  |     /// <param name="isPath">是否是带路径的标识符</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="bool" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public bool Remove(string identifier, bool isPath) => isPath ? RemovePathValue(identifier) : Remove(identifier); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     根据路径删除值 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="path">带路径的标识符</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="bool" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public bool RemovePathValue(string path) | ||||||
|  |     { | ||||||
|  |         // 空检查 | ||||||
|  |         ArgumentNullException.ThrowIfNull(path); | ||||||
|  |  | ||||||
|  |         // 根据路径分隔符进行分割,并确保至少有一个标识符 | ||||||
|  |         var identifiers = path.Split(Options.PathSeparator, StringSplitOptions.RemoveEmptyEntries); | ||||||
|  |         if (identifiers is { Length: 0 }) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 根据标识符查找 JsonNode 节点 | ||||||
|  |         var currentNode = FindNode(identifiers[0]); | ||||||
|  |         if (currentNode is null) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 遍历剩余的标识符 | ||||||
|  |         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 false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 从父节点删除 | ||||||
|  |         return ((Clay)DeserializeNode(currentNode.Parent, Options)!).Remove(identifiers[^1]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     根据标识符删除数据 |     ///     根据标识符删除数据 | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -960,6 +1021,25 @@ public partial class Clay | |||||||
|  |  | ||||||
|         return Remove(start, end); |         return Remove(start, end); | ||||||
|     } |     } | ||||||
|  |     /// <summary> | ||||||
|  |     ///     根据标识符(路径)删除数据 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="identifier">带路径的标识符</param> | ||||||
|  |     /// <param name="isPath">是否是带路径的标识符</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="bool" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public bool Delete(string identifier, bool isPath) => isPath ? RemovePathValue(identifier) : Remove(identifier); | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     ///     根据路径删除值 | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="path">带路径的标识符</param> | ||||||
|  |     /// <returns> | ||||||
|  |     ///     <see cref="bool" /> | ||||||
|  |     /// </returns> | ||||||
|  |     public bool DeletePathValue(string path) => RemovePathValue(path); | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     ///     将 <see cref="Clay" /> 转换为目标类型 |     ///     将 <see cref="Clay" /> 转换为目标类型 | ||||||
|   | |||||||
| @@ -95,6 +95,7 @@ public static class FS | |||||||
|         fileExtensionProvider.Mappings[".properties"] = "application/octet-stream"; |         fileExtensionProvider.Mappings[".properties"] = "application/octet-stream"; | ||||||
|         fileExtensionProvider.Mappings[".m3u8"] = "application/x-mpegURL"; |         fileExtensionProvider.Mappings[".m3u8"] = "application/x-mpegURL"; | ||||||
|         fileExtensionProvider.Mappings[".ofd"] = "application/ofd"; |         fileExtensionProvider.Mappings[".ofd"] = "application/ofd"; | ||||||
|  |         fileExtensionProvider.Mappings[".jsonl"] = "application/json-lines"; | ||||||
|         return fileExtensionProvider; |         return fileExtensionProvider; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1,3 +1,3 @@ | |||||||
| https://gitee.com/NewLifeX/X/commit/bcb90e8bffbfe998c0b6e9167c96c60e3ae6371f | https://gitee.com/NewLifeX/X/commit/a07810c61ba0e9dc792ab1af13ebf7c8a3a5d2da | ||||||
|  |  | ||||||
| 修改大部分CA规则,后期优化减少字符串操作 | 修改大部分CA规则,后期优化减少字符串操作 | ||||||
| @@ -13,13 +13,21 @@ public static class SpanHelper | |||||||
|     /// <param name="span"></param> |     /// <param name="span"></param> | ||||||
|     /// <param name="encoding"></param> |     /// <param name="encoding"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static String ToStr(this ReadOnlySpan<Byte> span, Encoding? encoding = null) => (encoding ?? Encoding.UTF8).GetString(span); |     public static String ToStr(this ReadOnlySpan<Byte> span, Encoding? encoding = null) | ||||||
|  |     { | ||||||
|  |         if (span.Length == 0) return String.Empty; | ||||||
|  |         return (encoding ?? Encoding.UTF8).GetString(span); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary>转字符串</summary> |     /// <summary>转字符串</summary> | ||||||
|     /// <param name="span"></param> |     /// <param name="span"></param> | ||||||
|     /// <param name="encoding"></param> |     /// <param name="encoding"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public static String ToStr(this Span<Byte> span, Encoding? encoding = null) => (encoding ?? Encoding.UTF8).GetString(span); |     public static String ToStr(this Span<Byte> span, Encoding? encoding = null) | ||||||
|  |     { | ||||||
|  |         if (span.Length == 0) return String.Empty; | ||||||
|  |         return (encoding ?? Encoding.UTF8).GetString(span); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary>获取字符串的字节数组</summary> |     /// <summary>获取字符串的字节数组</summary> | ||||||
|     public static unsafe Int32 GetBytes(this Encoding encoding, ReadOnlySpan<Char> chars, Span<Byte> bytes) |     public static unsafe Int32 GetBytes(this Encoding encoding, ReadOnlySpan<Char> chars, Span<Byte> bytes) | ||||||
|   | |||||||
| @@ -201,15 +201,17 @@ public class DefaultConvert | |||||||
|         // 特殊处理字符串,也是最常见的 |         // 特殊处理字符串,也是最常见的 | ||||||
|         if (value is String str) |         if (value is String str) | ||||||
|         { |         { | ||||||
|  |             if (Int32.TryParse(str, out var n)) return n; | ||||||
|  |  | ||||||
|             // 拷贝而来的逗号分隔整数 |             // 拷贝而来的逗号分隔整数 | ||||||
|             Span<Char> tmp = stackalloc Char[str.Length]; |             Span<Char> tmp = stackalloc Char[str.Length]; | ||||||
|             var rs = TrimNumber(str.AsSpan(), tmp); |             var rs = TrimNumber(str.AsSpan(), tmp); | ||||||
|             if (rs == 0) return defaultValue; |             if (rs == 0) return defaultValue; | ||||||
|  |  | ||||||
| #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | ||||||
|             return Int32.TryParse(tmp[..rs], out var n) ? n : defaultValue; |             return Int32.TryParse(tmp[..rs], out n) ? n : defaultValue; | ||||||
| #else | #else | ||||||
|             return Int32.TryParse(tmp[..rs].ToString(), out var n) ? n : defaultValue; |             return Int32.TryParse(tmp[..rs].ToString(), out n) ? n : defaultValue; | ||||||
| #endif | #endif | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -290,15 +292,17 @@ public class DefaultConvert | |||||||
|         // 特殊处理字符串,也是最常见的 |         // 特殊处理字符串,也是最常见的 | ||||||
|         if (value is String str) |         if (value is String str) | ||||||
|         { |         { | ||||||
|  |             if (Int64.TryParse(str, out var n)) return n; | ||||||
|  |  | ||||||
|             // 拷贝而来的逗号分隔整数 |             // 拷贝而来的逗号分隔整数 | ||||||
|             Span<Char> tmp = stackalloc Char[str.Length]; |             Span<Char> tmp = stackalloc Char[str.Length]; | ||||||
|             var rs = TrimNumber(str.AsSpan(), tmp); |             var rs = TrimNumber(str.AsSpan(), tmp); | ||||||
|             if (rs == 0) return defaultValue; |             if (rs == 0) return defaultValue; | ||||||
|  |  | ||||||
| #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | ||||||
|             return Int64.TryParse(tmp[..rs], out var n) ? n : defaultValue; |             return Int64.TryParse(tmp[..rs], out n) ? n : defaultValue; | ||||||
| #else | #else | ||||||
|             return Int64.TryParse(new String(tmp[..rs].ToArray()), out var n) ? n : defaultValue; |             return Int64.TryParse(new String(tmp[..rs].ToArray()), out n) ? n : defaultValue; | ||||||
| #endif | #endif | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -374,14 +378,16 @@ public class DefaultConvert | |||||||
|         // 特殊处理字符串,也是最常见的 |         // 特殊处理字符串,也是最常见的 | ||||||
|         if (value is String str) |         if (value is String str) | ||||||
|         { |         { | ||||||
|  |             if (Double.TryParse(str, out var n)) return n; | ||||||
|  |  | ||||||
|             Span<Char> tmp = stackalloc Char[str.Length]; |             Span<Char> tmp = stackalloc Char[str.Length]; | ||||||
|             var rs = TrimNumber(str.AsSpan(), tmp); |             var rs = TrimNumber(str.AsSpan(), tmp); | ||||||
|             if (rs == 0) return defaultValue; |             if (rs == 0) return defaultValue; | ||||||
|  |  | ||||||
| #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | ||||||
|             return Double.TryParse(tmp[..rs], out var n) ? n : defaultValue; |             return Double.TryParse(tmp[..rs], out n) ? n : defaultValue; | ||||||
| #else | #else | ||||||
|             return Double.TryParse(new String(tmp[..rs].ToArray()), out var n) ? n : defaultValue; |             return Double.TryParse(new String(tmp[..rs].ToArray()), out n) ? n : defaultValue; | ||||||
| #endif | #endif | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -422,14 +428,16 @@ public class DefaultConvert | |||||||
|         // 特殊处理字符串,也是最常见的 |         // 特殊处理字符串,也是最常见的 | ||||||
|         if (value is String str) |         if (value is String str) | ||||||
|         { |         { | ||||||
|  |             if (Decimal.TryParse(str, NumberStyles.Number | NumberStyles.AllowExponent, null, out var n)) return n; | ||||||
|  |  | ||||||
|             Span<Char> tmp = stackalloc Char[str.Length]; |             Span<Char> tmp = stackalloc Char[str.Length]; | ||||||
|             var rs = TrimNumber(str.AsSpan(), tmp); |             var rs = TrimNumber(str.AsSpan(), tmp); | ||||||
|             if (rs == 0) return defaultValue; |             if (rs == 0) return defaultValue; | ||||||
|  |  | ||||||
| #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER | ||||||
|             return Decimal.TryParse(tmp[..rs], out var n) ? n : defaultValue; |             return Decimal.TryParse(tmp[..rs], out n) ? n : defaultValue; | ||||||
| #else | #else | ||||||
|             return Decimal.TryParse(new String(tmp[..rs].ToArray()), out var n) ? n : defaultValue; |             return Decimal.TryParse(new String(tmp[..rs].ToArray()), out n) ? n : defaultValue; | ||||||
| #endif | #endif | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -577,11 +585,16 @@ public class DefaultConvert | |||||||
|  |  | ||||||
|             // 处理UTC |             // 处理UTC | ||||||
|             var utc = false; |             var utc = false; | ||||||
|             if (str.EndsWithIgnoreCase(" UTC") || str.EndsWith('Z') && str.Contains('T')) |             if (str.EndsWithIgnoreCase(" UTC")) | ||||||
|             { |             { | ||||||
|                 utc = true; |                 utc = true; | ||||||
|                 str = str[0..^4]; |                 str = str[0..^4]; | ||||||
|             } |             } | ||||||
|  |             else if (str.EndsWith('Z')) | ||||||
|  |             { | ||||||
|  |                 utc = true; | ||||||
|  |                 str = str[0..^1]; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             if (!DateTime.TryParse(str, out var dt) && |             if (!DateTime.TryParse(str, out var dt) && | ||||||
|                 !(str.Contains('-') && DateTime.TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt)) && |                 !(str.Contains('-') && DateTime.TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt)) && | ||||||
| @@ -715,9 +728,19 @@ public class DefaultConvert | |||||||
|                 ch = (Char)(input[i] - 0xFEE0); |                 ch = (Char)(input[i] - 0xFEE0); | ||||||
|  |  | ||||||
|             // 数字和小数点 以外字符,认为非数字 |             // 数字和小数点 以外字符,认为非数字 | ||||||
|             if (ch is not '.' and not '-' and (< '0' or > '9')) return 0; |             if (ch is '.' or '-' or not < '0' and not > '9') | ||||||
|  |                 output[idx++] = ch; | ||||||
|             output[idx++] = ch; |             else | ||||||
|  |             { | ||||||
|  |                 // 支持科学计数法,e/E后面可以跟正负号,之后至少跟一个数字 | ||||||
|  |                 if ((ch is 'e' or 'E') && idx > 0 && i + 2 < input.Length && (input[i + 1] is '+' or '-')) | ||||||
|  |                 { | ||||||
|  |                     output[idx++] = ch; | ||||||
|  |                     output[idx++] = input[++i]; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                     return 0; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return idx; |         return idx; | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System.ComponentModel; | using System.ComponentModel; | ||||||
|  | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| @@ -102,10 +103,13 @@ public static class PacketHelper | |||||||
|     { |     { | ||||||
|         // 总是有异常数据,这里屏蔽异常 |         // 总是有异常数据,这里屏蔽异常 | ||||||
|         if (pk == null) return null!; |         if (pk == null) return null!; | ||||||
|  |         if (pk.Total == 0) return String.Empty; | ||||||
|  |  | ||||||
|         if (pk.Next == null) |         if (pk.Next == null) | ||||||
|         { |         { | ||||||
|             if (count < 0) count = pk.Length - offset; |             if (count < 0) count = pk.Length - offset; | ||||||
|  |             if (count == 0) return String.Empty; | ||||||
|  |  | ||||||
|             var span = pk.GetSpan(); |             var span = pk.GetSpan(); | ||||||
|             if (span.Length > count) span = span[..count]; |             if (span.Length > count) span = span[..count]; | ||||||
|  |  | ||||||
| @@ -301,6 +305,32 @@ public static class PacketHelper | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary>尝试扩展头部,用于填充包头,减少内存分配</summary> | ||||||
|  |     /// <param name="pk">数据包</param> | ||||||
|  |     /// <param name="size">要扩大的头部大小,不包括负载数据</param> | ||||||
|  |     /// <param name="newPacket">扩展后的数据包</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     [Obsolete] | ||||||
|  |     public static Boolean TryExpandHeader(this IPacket pk, Int32 size, [NotNullWhen(true)] out IPacket? newPacket) | ||||||
|  |     { | ||||||
|  |         newPacket = null; | ||||||
|  |  | ||||||
|  |         if (pk is ArrayPacket ap && ap.Offset >= size) | ||||||
|  |         { | ||||||
|  |             newPacket = new ArrayPacket(ap.Buffer, ap.Offset - size, ap.Length + size) { Next = ap.Next }; | ||||||
|  |  | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         else if (pk is OwnerPacket owner && owner.Offset >= size) | ||||||
|  |         { | ||||||
|  |             newPacket = new OwnerPacket(owner, size); | ||||||
|  |  | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary>扩展头部,用于填充包头,减少内存分配</summary> |     /// <summary>扩展头部,用于填充包头,减少内存分配</summary> | ||||||
|     /// <param name="pk">数据包</param> |     /// <param name="pk">数据包</param> | ||||||
|     /// <param name="size">要扩大的头部大小,不包括负载数据</param> |     /// <param name="size">要扩大的头部大小,不包括负载数据</param> | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ public abstract class Logger : ILog | |||||||
|     /// <param name="format"></param> |     /// <param name="format"></param> | ||||||
|     /// <param name="args"></param> |     /// <param name="args"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     protected virtual String Format(String format, Object?[]? args) |     internal protected virtual String Format(String format, Object?[]? args) | ||||||
|     { |     { | ||||||
|         //处理时间的格式化 |         //处理时间的格式化 | ||||||
|         if (args?.Length > 0) |         if (args?.Length > 0) | ||||||
| @@ -70,7 +70,7 @@ public abstract class Logger : ILog | |||||||
|  |  | ||||||
|             for (var i = 0; i < args.Length; i++) |             for (var i = 0; i < args.Length; i++) | ||||||
|             { |             { | ||||||
|                 if (args[i] != null && args[i] is DateTime dt) |                 if (args[i] != null && args[i] is DateTime dt && format.Contains("{" + i + "}")) | ||||||
|                 { |                 { | ||||||
|                     // 根据时间值的精确度选择不同的格式化输出 |                     // 根据时间值的精确度选择不同的格式化输出 | ||||||
|                     //var dt = (DateTime)args[i]; |                     //var dt = (DateTime)args[i]; | ||||||
|   | |||||||
| @@ -37,6 +37,9 @@ public static class XTrace | |||||||
|     /// <param name="msg">信息</param> |     /// <param name="msg">信息</param> | ||||||
|     public static void WriteLine(String msg) |     public static void WriteLine(String msg) | ||||||
|     { |     { | ||||||
|  |         // 只过滤null,保留包含空格的字符串 | ||||||
|  |         if (msg == null) return; | ||||||
|  |  | ||||||
|         if (!InitLog()) return; |         if (!InitLog()) return; | ||||||
|  |  | ||||||
|         WriteVersion(); |         WriteVersion(); | ||||||
| @@ -49,6 +52,9 @@ public static class XTrace | |||||||
|     /// <param name="args"></param> |     /// <param name="args"></param> | ||||||
|     public static void WriteLine(String format, params Object?[] args) |     public static void WriteLine(String format, params Object?[] args) | ||||||
|     { |     { | ||||||
|  |         // 只过滤null,保留包含空格的字符串 | ||||||
|  |         if (format == null) return; | ||||||
|  |  | ||||||
|         if (!InitLog()) return; |         if (!InitLog()) return; | ||||||
|  |  | ||||||
|         WriteVersion(); |         WriteVersion(); | ||||||
|   | |||||||
| @@ -104,11 +104,14 @@ public abstract class Actor : DisposeBase, IActor | |||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|     #region 方法 |     #region 方法 | ||||||
|  |  | ||||||
|     /// <summary>通知开始处理</summary> |     /// <summary>通知开始处理</summary> | ||||||
|     /// <remarks> |     /// <remarks> | ||||||
|     /// 添加消息时自动触发 |     /// 添加消息时自动触发 | ||||||
|     /// </remarks> |     /// </remarks> | ||||||
|     public virtual Task? Start() |     /// <param name="cancellationToken">取消令牌。可用于通知内部取消工作</param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public virtual Task? Start(CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         if (Active) return _task; |         if (Active) return _task; | ||||||
|         lock (this) |         lock (this) | ||||||
| @@ -119,7 +122,7 @@ public abstract class Actor : DisposeBase, IActor | |||||||
|  |  | ||||||
|             using var span = Tracer?.NewSpan("actor:Start", Name); |             using var span = Tracer?.NewSpan("actor:Start", Name); | ||||||
|  |  | ||||||
|             _source = new CancellationTokenSource(); |             _source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | ||||||
|             MailBox ??= new BlockingCollection<ActorContext>(BoundedCapacity); |             MailBox ??= new BlockingCollection<ActorContext>(BoundedCapacity); | ||||||
|  |  | ||||||
|             // 启动异步 |             // 启动异步 | ||||||
| @@ -127,7 +130,7 @@ public abstract class Actor : DisposeBase, IActor | |||||||
|             { |             { | ||||||
|                 lock (this) |                 lock (this) | ||||||
|                 { |                 { | ||||||
|                     _task ??= OnStart(); |                     _task ??= OnStart(_source.Token); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -139,7 +142,13 @@ public abstract class Actor : DisposeBase, IActor | |||||||
|  |  | ||||||
|     /// <summary>开始时,返回执行线程包装任务</summary> |     /// <summary>开始时,返回执行线程包装任务</summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     protected virtual Task OnStart() => Task.Factory.StartNew(DoActorWork, LongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.None); |     protected virtual Task OnStart(CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         var creationOptions = LongRunning ? TaskCreationOptions.LongRunning : TaskCreationOptions.None; | ||||||
|  |         var scheduler = TaskScheduler.Current ?? TaskScheduler.Default; | ||||||
|  |         return Task.Factory.StartNew(DoActorWork, cancellationToken, creationOptions, scheduler); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /// <summary>通知停止添加消息,并等待处理完成</summary> |     /// <summary>通知停止添加消息,并等待处理完成</summary> | ||||||
|     /// <param name="msTimeout">等待的毫秒数。0表示不等待,-1表示无限等待</param> |     /// <param name="msTimeout">等待的毫秒数。0表示不等待,-1表示无限等待</param> | ||||||
|   | |||||||
| @@ -330,7 +330,7 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature | |||||||
|     { |     { | ||||||
|         EnsureCreateServer(); |         EnsureCreateServer(); | ||||||
|  |  | ||||||
|         if (Servers.Count == 0) throw new Exception("Failed to listen to all ports!"); |         if (Servers.Count == 0) throw new Exception($"Failed to listen to all ports! Port=[{Port}]"); | ||||||
|  |  | ||||||
|         WriteLog("准备开始监听{0}个服务器", Servers.Count); |         WriteLog("准备开始监听{0}个服务器", Servers.Count); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -724,14 +724,22 @@ public class DefaultReflect : IReflect | |||||||
|                     return value.ToDateTime(); |                     return value.ToDateTime(); | ||||||
|                 case TypeCode.Double: |                 case TypeCode.Double: | ||||||
|                     return value.ToDouble(); |                     return value.ToDouble(); | ||||||
|  |                 case TypeCode.Single: | ||||||
|  |                     return (Single)value.ToDouble(); | ||||||
|  |                 case TypeCode.Decimal: | ||||||
|  |                     return value.ToDecimal(); | ||||||
|                 case TypeCode.Int16: |                 case TypeCode.Int16: | ||||||
|                     return (Int16)value.ToInt(); |                     return (Int16)value.ToInt(); | ||||||
|                 case TypeCode.Int32: |                 case TypeCode.Int32: | ||||||
|                     return value.ToInt(); |                     return value.ToInt(); | ||||||
|  |                 case TypeCode.Int64: | ||||||
|  |                     return value.ToLong(); | ||||||
|                 case TypeCode.UInt16: |                 case TypeCode.UInt16: | ||||||
|                     return (UInt16)value.ToInt(); |                     return (UInt16)value.ToInt(); | ||||||
|                 case TypeCode.UInt32: |                 case TypeCode.UInt32: | ||||||
|                     return (UInt32)value.ToInt(); |                     return (UInt32)value.ToInt(); | ||||||
|  |                 case TypeCode.UInt64: | ||||||
|  |                     return (UInt64)value.ToLong(); | ||||||
|                 default: |                 default: | ||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
| @@ -758,8 +766,20 @@ public class DefaultReflect : IReflect | |||||||
|                 // 支持IParsable<TSelf>接口 |                 // 支持IParsable<TSelf>接口 | ||||||
|                 if (conversionType.GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IParsable<>))) |                 if (conversionType.GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IParsable<>))) | ||||||
|                 { |                 { | ||||||
|                     var mi = conversionType.GetMethod("Parse", [typeof(String), typeof(IFormatProvider)]); |                     // 获取 TryParse 静态方法 | ||||||
|                     if (mi != null) return mi.Invoke(null, [value, null]); |                     var tryParse = conversionType.GetMethod("TryParse", [typeof(String), typeof(IFormatProvider), conversionType.MakeByRefType()]); | ||||||
|  |                     if (tryParse != null) | ||||||
|  |                     { | ||||||
|  |                         var parameters = new Object?[] { str, null, null }; | ||||||
|  |                         var success = (Boolean)tryParse.Invoke(null, parameters)!; | ||||||
|  |                         if (success) return parameters[2]; | ||||||
|  |                         //return null; | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         var mi = conversionType.GetMethod("Parse", [typeof(String), typeof(IFormatProvider)]); | ||||||
|  |                         if (mi != null) return mi.Invoke(null, [value, null]); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| #endif | #endif | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -10,7 +10,8 @@ | |||||||
| 		<AssemblyTitle>工具核心库</AssemblyTitle> | 		<AssemblyTitle>工具核心库</AssemblyTitle> | ||||||
| 		<Description>ThingsGateway.NewLife.X</Description> | 		<Description>ThingsGateway.NewLife.X</Description> | ||||||
| 		<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | 		<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||||
|  | 		<SignAssembly>True</SignAssembly> | ||||||
|  | 		<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile> | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|  |  | ||||||
| 	<ItemGroup> | 	<ItemGroup> | ||||||
|   | |||||||
| @@ -32,8 +32,12 @@ public static class PluginHelper | |||||||
|         var file = string.Empty; |         var file = string.Empty; | ||||||
|         if (!dll.IsNullOrEmpty()) |         if (!dll.IsNullOrEmpty()) | ||||||
|         { |         { | ||||||
|             // 先检查当前目录,再检查插件目录 |             // 先检查程序集所在目录,再检查当前目录、基准目录和插件目录。在应用发布时,插件很可能跟常规应用程序集放在同一目录下 | ||||||
|             file = dll.GetCurrentPath(); |             var exe = Assembly.GetEntryAssembly()?.Location; | ||||||
|  |             if (exe.IsNullOrEmpty()) exe = Assembly.GetCallingAssembly()?.Location; | ||||||
|  |             if (exe.IsNullOrEmpty()) exe = Assembly.GetExecutingAssembly()?.Location; | ||||||
|  |             if (!exe.IsNullOrEmpty()) file = Path.GetDirectoryName(exe).CombinePath(dll).GetFullPath(); | ||||||
|  |             if (!File.Exists(file)) file = dll.GetCurrentPath(); | ||||||
|             if (!File.Exists(file)) file = dll.GetFullPath(); |             if (!File.Exists(file)) file = dll.GetFullPath(); | ||||||
|             if (!File.Exists(file)) file = dll.GetBasePath(); |             if (!File.Exists(file)) file = dll.GetBasePath(); | ||||||
|             if (!File.Exists(file)) file = set.PluginPath.CombinePath(dll).GetFullPath(); |             if (!File.Exists(file)) file = set.PluginPath.CombinePath(dll).GetFullPath(); | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| /// <summary>资源定位。无限制解析Url地址</summary> | /// <summary>资源定位。无限制解析Url地址</summary> | ||||||
| public class UriInfo | public class UriInfo | ||||||
| { | { | ||||||
|  |     #region 属性 | ||||||
|     /// <summary>协议</summary> |     /// <summary>协议</summary> | ||||||
|     public String? Scheme { get; set; } |     public String? Scheme { get; set; } | ||||||
|  |  | ||||||
| @@ -31,10 +32,6 @@ public class UriInfo | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary>实例化</summary> |  | ||||||
|     /// <param name="value"></param> |  | ||||||
|     public UriInfo(String value) => Parse(value); |  | ||||||
|  |  | ||||||
|     /// <summary>主机与端口。省略默认端口</summary> |     /// <summary>主机与端口。省略默认端口</summary> | ||||||
|     public String? Authority |     public String? Authority | ||||||
|     { |     { | ||||||
| @@ -51,12 +48,33 @@ public class UriInfo | |||||||
|             return $"{Host}:{Port}"; |             return $"{Host}:{Port}"; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     #endregion | ||||||
|  |  | ||||||
|  |     #region 构造 | ||||||
|  |     /// <summary>实例化</summary> | ||||||
|  |     public UriInfo() { } | ||||||
|  |  | ||||||
|  |     /// <summary>实例化</summary> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     public UriInfo(String value) => Parse(value); | ||||||
|  |     #endregion | ||||||
|  |  | ||||||
|  |     #region 方法 | ||||||
|  |     /// <summary>尝试解析Url字符串</summary> | ||||||
|  |     public static Boolean TryParse(String? value, out UriInfo? uriInfo) | ||||||
|  |     { | ||||||
|  |         uriInfo = null; | ||||||
|  |         if (value.IsNullOrWhiteSpace()) return false; | ||||||
|  |  | ||||||
|  |         uriInfo = new UriInfo(); | ||||||
|  |         return uriInfo.Parse(value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary>解析Url字符串</summary> |     /// <summary>解析Url字符串</summary> | ||||||
|     /// <param name="value"></param> |     /// <param name="value"></param> | ||||||
|     public void Parse(String value) |     public Boolean Parse(String value) | ||||||
|     { |     { | ||||||
|         if (value.IsNullOrWhiteSpace()) return; |         if (value.IsNullOrWhiteSpace()) return false; | ||||||
|  |  | ||||||
|         // 先处理头尾,再处理中间的主机和端口 |         // 先处理头尾,再处理中间的主机和端口 | ||||||
|         var p = value.IndexOf("://"); |         var p = value.IndexOf("://"); | ||||||
| @@ -89,7 +107,13 @@ public class UriInfo | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // 如果主要部分都没有,标记为失败 | ||||||
|  |         if (Scheme.IsNullOrEmpty() && Host.IsNullOrEmpty() && AbsolutePath.IsNullOrEmpty()) | ||||||
|  |             return false; | ||||||
|  |  | ||||||
|         if (AbsolutePath.IsNullOrEmpty()) AbsolutePath = "/"; |         if (AbsolutePath.IsNullOrEmpty()) AbsolutePath = "/"; | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void ParsePath(String value, Int32 p) |     private void ParsePath(String value, Int32 p) | ||||||
| @@ -122,6 +146,32 @@ public class UriInfo | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary>拼接请求参数</summary> | ||||||
|  |     /// <param name="name"></param> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public UriInfo Append(String name, Object? value) | ||||||
|  |     { | ||||||
|  |         var q = Query; | ||||||
|  |         Query = q.IsNullOrEmpty() ? $"{name}={value}" : $"{q}&{name}={value}"; | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary>拼接请求参数(非空)</summary> | ||||||
|  |     /// <param name="name"></param> | ||||||
|  |     /// <param name="value"></param> | ||||||
|  |     /// <returns></returns> | ||||||
|  |     public UriInfo AppendNotEmpty(String name, Object? value) | ||||||
|  |     { | ||||||
|  |         if (value?.ToString().IsNullOrEmpty() != false) return this; | ||||||
|  |  | ||||||
|  |         var q = Query; | ||||||
|  |         Query = q.IsNullOrEmpty() ? $"{name}={value}" : $"{q}&{name}={value}"; | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary>已重载。</summary> |     /// <summary>已重载。</summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public override String? ToString() |     public override String? ToString() | ||||||
| @@ -136,4 +186,5 @@ public class UriInfo | |||||||
|  |  | ||||||
|         return $"{Scheme}://{authority}{PathAndQuery}"; |         return $"{Scheme}://{authority}{PathAndQuery}"; | ||||||
|     } |     } | ||||||
|  |     #endregion | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								src/Admin/ThingsGateway.NewLife.X/newlife.snk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/Admin/ThingsGateway.NewLife.X/newlife.snk
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user
	 2248356998 qq.com
					2248356998 qq.com