文档相关修复
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SimApi.Configurations;
|
||||
|
||||
namespace SimApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 将 SimApiRouteOptions 动态配置的内置约定路由(UserInfo / Logout / WebConfig)
|
||||
/// 翻译成 ApiDescription, 使它们能像属性路由接口一样出现在 Swagger 文档中:
|
||||
/// 请求/响应 schema 由 Swashbuckle 自动分析, 方法上已有的 [SimApiDoc] 注解自动生效。
|
||||
/// 路径为 null / 空 的端点在此一并跳过(与路由注册保持一致)。
|
||||
/// </summary>
|
||||
public class SimApiBuiltInRoutesDescriptionProvider(
|
||||
IServiceProvider services,
|
||||
SimApiOptions options) : IApiDescriptionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认 provider(DefaultApiDescriptionProvider)的 Order 为 -1000, 位于其后执行。
|
||||
/// </summary>
|
||||
public int Order => -900;
|
||||
|
||||
public void OnProvidersExecuting(ApiDescriptionProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnProvidersExecuted(ApiDescriptionProviderContext context)
|
||||
{
|
||||
var modelMetadata = services.GetService<IModelMetadataProvider>();
|
||||
|
||||
foreach (var route in SimApiBuiltInRoutes.Get(options.SimApiRouteOptions))
|
||||
{
|
||||
var action = context.Actions.OfType<ControllerActionDescriptor>()
|
||||
.FirstOrDefault(a =>
|
||||
string.Equals(a.ControllerName, route.Controller, StringComparison.Ordinal) &&
|
||||
string.Equals(a.ActionName, route.Action, StringComparison.Ordinal));
|
||||
if (action is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 若该动作已被属性路由 / 其他 provider 收录, 跳过避免重复
|
||||
if (context.Results.Any(d => ReferenceEquals(d.ActionDescriptor, action)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var httpMethod in route.HttpMethods)
|
||||
{
|
||||
context.Results.Add(CreateDescription(action, route, httpMethod, modelMetadata));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ApiDescription CreateDescription(
|
||||
ControllerActionDescriptor action,
|
||||
SimApiBuiltInRoute route,
|
||||
string httpMethod,
|
||||
IModelMetadataProvider? modelMetadata)
|
||||
{
|
||||
var apiDescription = new ApiDescription
|
||||
{
|
||||
ActionDescriptor = action,
|
||||
HttpMethod = httpMethod,
|
||||
RelativePath = route.Path.TrimStart('/')
|
||||
};
|
||||
|
||||
// 输入: 按真实方法参数翻译(内置端点均为零参, 此处保证将来加参也可分析)
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
var parameterDescription = new ApiParameterDescription
|
||||
{
|
||||
Name = parameter.Name,
|
||||
Type = parameter.ParameterType,
|
||||
ParameterDescriptor = parameter,
|
||||
Source = parameter.BindingInfo?.BindingSource ?? BindingSource.Body,
|
||||
IsRequired = true
|
||||
};
|
||||
if (modelMetadata is not null)
|
||||
{
|
||||
parameterDescription.ModelMetadata = modelMetadata.GetMetadataForType(parameter.ParameterType);
|
||||
}
|
||||
|
||||
apiDescription.ParameterDescriptions.Add(parameterDescription);
|
||||
}
|
||||
|
||||
// 输出: 方法返回类型 => 200 + json
|
||||
var returnType = Unwrap(action.MethodInfo.ReturnType);
|
||||
if (returnType != typeof(void))
|
||||
{
|
||||
var responseType = new ApiResponseType
|
||||
{
|
||||
StatusCode = 200,
|
||||
Type = returnType,
|
||||
IsDefaultResponse = true
|
||||
};
|
||||
if (modelMetadata is not null)
|
||||
{
|
||||
responseType.ModelMetadata = modelMetadata.GetMetadataForType(returnType);
|
||||
}
|
||||
|
||||
responseType.ApiResponseFormats.Add(new ApiResponseFormat { MediaType = "application/json" });
|
||||
apiDescription.SupportedResponseTypes.Add(responseType);
|
||||
}
|
||||
|
||||
return apiDescription;
|
||||
}
|
||||
|
||||
private static Type Unwrap(Type type) =>
|
||||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)
|
||||
? type.GetGenericArguments()[0]
|
||||
: type;
|
||||
}
|
||||
Reference in New Issue
Block a user