diff --git a/src/Senparc.CO2NET.Cache.CsRedis.Tests/InitTests.cs b/src/Senparc.CO2NET.Cache.CsRedis.Tests/InitTests.cs index fb39a7bb..dd1236fb 100644 --- a/src/Senparc.CO2NET.Cache.CsRedis.Tests/InitTests.cs +++ b/src/Senparc.CO2NET.Cache.CsRedis.Tests/InitTests.cs @@ -52,7 +52,7 @@ public void AutoRegisterConfigurationTest() Assert.AreEqual(null, RedisManager.ConfigurationOption);// Not registered yet Register.SetConfigurationOption(redisServer); -\ Assert.AreEqual(redisServer, RedisManager.ConfigurationOption); + Assert.AreEqual(redisServer, RedisManager.ConfigurationOption); var currentCache = CacheStrategyFactory.GetObjectCacheStrategyInstance(); Assert.IsInstanceOfType(currentCache, typeof(RedisObjectCacheStrategy)); diff --git a/src/Senparc.CO2NET.WebApi.Tests/Senparc.CO2NET.WebApi.Tests.csproj b/src/Senparc.CO2NET.WebApi.Tests/Senparc.CO2NET.WebApi.Tests.csproj index c7e5267d..21a555c0 100644 --- a/src/Senparc.CO2NET.WebApi.Tests/Senparc.CO2NET.WebApi.Tests.csproj +++ b/src/Senparc.CO2NET.WebApi.Tests/Senparc.CO2NET.WebApi.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false @@ -14,7 +14,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Senparc.CO2NET.WebApi/Entities/DocMethodInfo.cs b/src/Senparc.CO2NET.WebApi/Entities/DocMethodInfo.cs index d02bc27c..a21d025b 100644 --- a/src/Senparc.CO2NET.WebApi/Entities/DocMethodInfo.cs +++ b/src/Senparc.CO2NET.WebApi/Entities/DocMethodInfo.cs @@ -1,18 +1,238 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace Senparc.CO2NET.WebApi { public class DocMethodInfo { - public DocMethodInfo(string methodName, string paramsPart) + /// + /// 初始化 DocMethodInfo + /// + /// 方法名称 + /// 参数部分的完整字符串 + /// 方法概要说明 + /// 参数字典,key: 参数名,value: 参数说明 + /// 返回值说明 + public DocMethodInfo(string methodName, string paramsPart, string summary = null, Dictionary parameters = null, string returns = null) { - MethodName = methodName; - ParamsPart = paramsPart; + MethodName = methodName?.Trim(); + ParamsPart = paramsPart?.Trim(); + Summary = summary?.Trim(); + Parameters = parameters ?? new Dictionary(); + Returns = returns?.Trim(); + + // 初始化其他属性 + IsAsync = CheckIsAsyncMethod(MethodName, ParamsPart); + HasParameters = !string.IsNullOrEmpty(ParamsPart) && ParamsPart != "()"; + HasReturnValue = !string.IsNullOrEmpty(Returns); + ParameterCount = Parameters?.Count ?? 0; } + /// + /// 方法名称 + /// public string MethodName { get; } + + /// + /// 参数部分的完整字符串 + /// public string ParamsPart { get; } + + /// + /// 方法概要说明 + /// + public string Summary { get; } + + /// + /// 参数字典,key: 参数名,value: 参数说明 + /// + public Dictionary Parameters { get; } + + /// + /// 返回值说明 + /// + public string Returns { get; } + + /// + /// 是否为异步方法 + /// + public bool IsAsync { get; } + + /// + /// 是否包含参数 + /// + public bool HasParameters { get; } + + /// + /// 是否有返回值说明 + /// + public bool HasReturnValue { get; } + + /// + /// 参数数量 + /// + public int ParameterCount { get; } + + /// + /// 获取格式化后的方法签名 + /// + /// + /// + /// 获取合并后的参数信息字符串 + /// + /// 是否包含参数类型信息 + /// 是否包含参数描述 + /// 格式化后的参数信息 + public string GetMergedParameters(bool includeParamsPart = true, bool includeDescription = true) + { + if (!HasParameters) + { + return "()"; + } + + var sb = new StringBuilder(); + + // 解析 ParamsPart,移除开头的 ( 和结尾的 ) + var paramTypes = ParamsPart.Trim('(', ')').Split(',') + .Select(p => p.Trim()) + .ToList(); + + // 获取参数名列表 + var paramNames = Parameters.Keys.ToList(); + + // 确保参数数量匹配 + if (paramTypes.Count != paramNames.Count) + { + return ParamsPart; // 如果不匹配,返回原始的 ParamsPart + } + + sb.Append('('); + for (int i = 0; i < paramNames.Count; i++) + { + if (i > 0) + { + sb.Append(", "); + } + + var paramName = paramNames[i]; + var paramType = paramTypes[i]; + + // 添加参数类型(如果需要) + if (includeParamsPart) + { + sb.Append(paramType).Append(' '); + } + + // 添加参数名 + sb.Append(paramName); + + // 添加参数描述(如果需要) + if (includeDescription && Parameters.ContainsKey(paramName)) + { + sb.Append(" /* ").Append(Parameters[paramName]).Append(" */"); + } + } + sb.Append(')'); + + return sb.ToString(); + } + + /// + /// 检查方法是否为异步方法 + /// + /// 方法名 + /// 参数部分 + /// + private bool CheckIsAsyncMethod(string methodName, string paramsPart) + { + if (string.IsNullOrEmpty(methodName)) + { + return false; + } + + // 1. 检查方法名是否以Async结尾 + bool isAsyncByName = methodName.EndsWith("Async", StringComparison.OrdinalIgnoreCase); + + // 2. 检查方法名是否包含泛型异步标记 + bool isAsyncByGeneric = methodName.Contains("Async``", StringComparison.OrdinalIgnoreCase); + + // 3. 检查返回值类型是否为异步类型 + bool isAsyncByReturnType = false; + if (!string.IsNullOrEmpty(Returns)) + { + var asyncTypes = new[] + { + "Task", + "Task<", + "ValueTask", + "ValueTask<", + "IAsyncEnumerable", + "IAsyncEnumerable<", + "System.Threading.Tasks.Task", + "System.Threading.Tasks.Task<", + "System.Threading.Tasks.ValueTask", + "System.Threading.Tasks.ValueTask<", + "System.Collections.Generic.IAsyncEnumerable", + "System.Collections.Generic.IAsyncEnumerable<" + }; + + isAsyncByReturnType = asyncTypes.Any(t => Returns.Contains(t, StringComparison.OrdinalIgnoreCase)); + } + + // 4. 检查参数中是否包含 CancellationToken(通常异步方法会有这个参数) + bool hasCancellationToken = false; + if (!string.IsNullOrEmpty(paramsPart)) + { + hasCancellationToken = paramsPart.Contains("CancellationToken", StringComparison.OrdinalIgnoreCase) || + paramsPart.Contains("System.Threading.CancellationToken", StringComparison.OrdinalIgnoreCase); + } + + // 返回综合判断结果 + return isAsyncByName || isAsyncByGeneric || isAsyncByReturnType || hasCancellationToken; + } + + public override string ToString() + { + var sb = new StringBuilder(); + + // 添加方法签名 + sb.AppendLine($"Method: {MethodName}{ParamsPart}"); + + // 添加异步标记 + if (IsAsync) + { + sb.AppendLine("Type: Async"); + } + + // 添加概要信息 + if (!string.IsNullOrEmpty(Summary)) + { + sb.AppendLine($"Summary: {Summary}"); + } + + // 添加参数信息 + if (HasParameters) + { + sb.AppendLine($"Parameters ({ParameterCount}):"); + foreach (var param in Parameters) + { + sb.AppendLine($" - {param.Key}: {param.Value}"); + } + } + else + { + sb.AppendLine("Parameters: None"); + } + + // 添加返回值信息 + if (HasReturnValue) + { + sb.AppendLine($"Returns: {Returns}"); + } + + return sb.ToString().TrimEnd(); + } } } diff --git a/src/Senparc.CO2NET.WebApi/Senparc.CO2NET.WebApi.csproj b/src/Senparc.CO2NET.WebApi/Senparc.CO2NET.WebApi.csproj index 0f58a0f3..cd38970d 100644 --- a/src/Senparc.CO2NET.WebApi/Senparc.CO2NET.WebApi.csproj +++ b/src/Senparc.CO2NET.WebApi/Senparc.CO2NET.WebApi.csproj @@ -1,7 +1,7 @@ netstandard2.1;net8.0 - 2.1.2 + 2.1.2.1 latest Senparc.CO2NET.WebApi Senparc.CO2NET.WebApi @@ -49,6 +49,7 @@ [2024-11-28] v2.0.2-beta3 Add UseLowerCaseApiName property for SenparcSetting [2024-12-04] v2.1.0-beta3 update Start() method, set SenparcSetting in Config when AddSenparcGlobalService() run [2025-08-20] v2.1.2 feat: Add default value GET to DefaultRequestMethod proprety + [2025-08-20] v2.1.2.1 Update async detection logic in DocMethodInfo and increment project version diff --git a/src/Senparc.CO2NET.WebApi/WebApiEngines/WebApiEngine.Doc.cs b/src/Senparc.CO2NET.WebApi/WebApiEngines/WebApiEngine.Doc.cs index de578059..9ca341f3 100644 --- a/src/Senparc.CO2NET.WebApi/WebApiEngines/WebApiEngine.Doc.cs +++ b/src/Senparc.CO2NET.WebApi/WebApiEngines/WebApiEngine.Doc.cs @@ -66,11 +66,36 @@ internal void TryCreateDir(string appDataPath) /// public DocMethodInfo GetDocMethodInfo(XAttribute nameAttr) { - //var pattern = @"(M\:)(?[^(]+)(?\({1}.+\){1})"; var result = regexForDoc.Match(nameAttr.Value); if (result.Success && result.Groups["docName"] != null && result.Groups["paramsPart"] != null) { - return new DocMethodInfo(result.Groups["docName"].Value, result.Groups["paramsPart"].Value); + var docName = result.Groups["docName"].Value; + var paramsPart = result.Groups["paramsPart"].Value; + + // Get parent element to access summary, params and returns + var memberElement = nameAttr.Parent; + if (memberElement != null) + { + var summary = memberElement.Element("summary")?.Value?.Trim(); + var returns = memberElement.Element("returns")?.Value?.Trim(); + var parameters = new Dictionary(); + + // Extract all param elements + var paramElements = memberElement.Elements("param"); + foreach (var paramElement in paramElements) + { + var paramName = paramElement.Attribute("name")?.Value; + var paramDescription = paramElement.Value?.Trim(); + if (!string.IsNullOrEmpty(paramName)) + { + parameters[paramName] = paramDescription; + } + } + + return new DocMethodInfo(docName, paramsPart, summary, parameters, returns); + } + + return new DocMethodInfo(docName, paramsPart); } return new DocMethodInfo(null, null); @@ -222,10 +247,12 @@ private async Task TryGetApiXmlInfo(string category, string sourceAs //Console.WriteLine("record docMembersCollection:" + docMethodInfo.MethodName); //Record interface information for search - var isAsync = docMethodInfo.MethodName.EndsWith("Async", StringComparison.OrdinalIgnoreCase) || - docMethodInfo.MethodName.Contains("Async``", StringComparison.OrdinalIgnoreCase);//Is it an asynchronous method - _findWeixinApiService.Value.RecordApiItem(category, docMethodInfo.MethodName, docMethodInfo.ParamsPart, - x.Element("summary")?.Value, isAsync); + //var isAsync = docMethodInfo.MethodName.EndsWith("Async", StringComparison.OrdinalIgnoreCase) || + // docMethodInfo.MethodName.Contains("Async``", StringComparison.OrdinalIgnoreCase);//Is it an asynchronous method + + var docMethodParams = docMethodInfo.GetMergedParameters(); + _findWeixinApiService.Value.RecordApiItem(category, docMethodInfo.MethodName, docMethodParams, + docMethodInfo.Summary, docMethodInfo.IsAsync/* isAsync*/); } } }