Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Senparc.CO2NET.Cache.CsRedis.Tests/InitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand All @@ -14,7 +14,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Senparc.Weixin.MP" Version="16.23.6" />
<!--<PackageReference Include="Senparc.Weixin.Open" Version="4.10.0-beta1" />
<PackageReference Include="Senparc.Weixin.WxOpen" Version="3.12.0-beta1" />-->
Expand Down
226 changes: 223 additions & 3 deletions src/Senparc.CO2NET.WebApi/Entities/DocMethodInfo.cs
Original file line number Diff line number Diff line change
@@ -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)
/// <summary>
/// 初始化 DocMethodInfo
/// </summary>
/// <param name="methodName">方法名称</param>
/// <param name="paramsPart">参数部分的完整字符串</param>
/// <param name="summary">方法概要说明</param>
/// <param name="parameters">参数字典,key: 参数名,value: 参数说明</param>
/// <param name="returns">返回值说明</param>
public DocMethodInfo(string methodName, string paramsPart, string summary = null, Dictionary<string, string> parameters = null, string returns = null)
{
MethodName = methodName;
ParamsPart = paramsPart;
MethodName = methodName?.Trim();
ParamsPart = paramsPart?.Trim();
Summary = summary?.Trim();
Parameters = parameters ?? new Dictionary<string, string>();
Returns = returns?.Trim();

// 初始化其他属性
IsAsync = CheckIsAsyncMethod(MethodName, ParamsPart);
HasParameters = !string.IsNullOrEmpty(ParamsPart) && ParamsPart != "()";
HasReturnValue = !string.IsNullOrEmpty(Returns);
ParameterCount = Parameters?.Count ?? 0;
}

/// <summary>
/// 方法名称
/// </summary>
public string MethodName { get; }

/// <summary>
/// 参数部分的完整字符串
/// </summary>
public string ParamsPart { get; }

/// <summary>
/// 方法概要说明
/// </summary>
public string Summary { get; }

/// <summary>
/// 参数字典,key: 参数名,value: 参数说明
/// </summary>
public Dictionary<string, string> Parameters { get; }

/// <summary>
/// 返回值说明
/// </summary>
public string Returns { get; }

/// <summary>
/// 是否为异步方法
/// </summary>
public bool IsAsync { get; }

/// <summary>
/// 是否包含参数
/// </summary>
public bool HasParameters { get; }

/// <summary>
/// 是否有返回值说明
/// </summary>
public bool HasReturnValue { get; }

/// <summary>
/// 参数数量
/// </summary>
public int ParameterCount { get; }

/// <summary>
/// 获取格式化后的方法签名
/// </summary>
/// <returns></returns>
/// <summary>
/// 获取合并后的参数信息字符串
/// </summary>
/// <param name="includeParamsPart">是否包含参数类型信息</param>
/// <param name="includeDescription">是否包含参数描述</param>
/// <returns>格式化后的参数信息</returns>
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();
}

/// <summary>
/// 检查方法是否为异步方法
/// </summary>
/// <param name="methodName">方法名</param>
/// <param name="paramsPart">参数部分</param>
/// <returns></returns>
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();
}
}
}
3 changes: 2 additions & 1 deletion src/Senparc.CO2NET.WebApi/Senparc.CO2NET.WebApi.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net8.0</TargetFrameworks>
<Version>2.1.2</Version>
<Version>2.1.2.1</Version>
<LangVersion>latest</LangVersion>
<AssemblyName>Senparc.CO2NET.WebApi</AssemblyName>
<RootNamespace>Senparc.CO2NET.WebApi</RootNamespace>
Expand Down Expand Up @@ -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
</PackageReleaseNotes>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
Expand Down
39 changes: 33 additions & 6 deletions src/Senparc.CO2NET.WebApi/WebApiEngines/WebApiEngine.Doc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,36 @@ internal void TryCreateDir(string appDataPath)
/// <returns></returns>
public DocMethodInfo GetDocMethodInfo(XAttribute nameAttr)
{
//var pattern = @"(M\:)(?<docName>[^(]+)(?<paramsPart>\({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<string, string>();

// 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);
Expand Down Expand Up @@ -222,10 +247,12 @@ private async Task<ApiXmlInfo> 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*/);
}
}
}
Expand Down