使用 .Net 5 REST API 大摇大摆。
我有一个包含数百种方法的 API,需要将相关 API 组合在一起使用。同时,我需要开始开发我的 API 的 V2 版本。我花了几个小时尝试不同的方法来实现版本控制和分组,但没有成功。
我有几个控制器,一些用于V1,一些用于V2。有人可以提供一个完整的例子吗?
在进入解决方案之前,让我确定几点:
ApiDescription.GroupName
在每个组的单个文档中整理 API,映射到 UI 中的下拉列表。改变这种行为是可能的,但并非微不足道。ApiDescription.GroupName
设置为格式化的 API 版本值,如果没有另外设置,以便 API 按版本分组。在4.1
之前,[ApiExplorerSettings(GroupName="...")]
被忽略并被覆盖。归根结底,您需要两个分组维度:category 和API version。不幸的是,API Explorer、OpenAPI/Swagger UI 和 Swashbuckle 仅在本质上支持其中一种。开箱即用不支持任何形式的层次结构。有几种方法可以解决这个问题:
group name
和API version
作为一个维度GroupName
更新回您想要的方式(这种方法不推荐)有可能更多的选择,但这些是最明智的。虽然它 可能不是您所希望的,但考虑到工作的限制,
#3
是最直接的实施方式。它看起来像这样:
public class SubgroupDescriptionProvider : IApiDescriptionProvider
{
private readonly IOptions<ApiExplorerOptions> options;
public SubgroupDescriptionProvider(IOptions<ApiExplorerOptions> options)
=> this.options = options;
// Execute after DefaultApiVersionDescriptionProvider.OnProvidersExecuted
public int Order => -1;
public void OnProvidersExecuting(ApiDescriptionProviderContext context) { }
public void OnProvidersExecuted(ApiDescriptionProviderContext context)
{
var format = options.Value.GroupNameFormat;
var culture = CultureInfo.CurrentCulture;
var results = context.Results;
var newResults = new List<ApiDescription>(capacity: results.Count);
for (var i = 0; i < results.Count; i++)
{
var result = results[i];
var apiVersion = result.GetApiVersion();
var versionGroupName = apiVersion.ToString(format, culture);
// [ApiExplorerSettings(GroupName="...")] was NOT set so
// nothing else to do
if (result.GroupName == versionGroupName)
{
continue;
}
// must be using [ApiExplorerSettings(GroupName="...")] so
// concatenate it with the formatted API version
result.GroupName += " " + versionGroupName;
// optional: add version grouping as well
// note: this works because the api description will appear in
// multiple, but different, documents
var newResult = result.Clone();
newResult.GroupName = versionGroupName;
newResults.Add(newResult);
}
newResults.ForEach(results.Add);
}
}
然后您使用 DI 注册它:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, SubgroupDescriptionProvider>());
如果你同时使用两组分组,那么你将得到多个 OpenAPI 文档。
按 API 版本(默认)
├─ v1
│ ├─ /utility
│ └─ /not-utility
└─ v2
├─ /utility
└─ /not-utility
按组名+API版本
├─ Utilities v1
│ └─ /utility
├─ Not Utilities v1
│ └─ /not-utility
├─ Utilities v2
│ └─ /utility
└─ Not Utilities v2
└─ /not-utility
如果您的版本控制策略类似于
N-2
,那么列表应该是合理的。是group * version
的产物,所以这个榜单就看你最终有多少组和多少版本了
拥有一个真正的二维层次结构应该在技术上是可行的,但我从来没有做过,也不知道有人愿意付出努力来实现它。该解决方案将非常具体您的 API 集。
在 program.cs (.net 6) 中使用以下代码,如果您使用的是 .net 5(或以下版本),请在 strartup.cs 中使用此代码
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VV";
options.SubstituteApiVersionInUrl = true;
});
services.AddSwaggerGen(options =>
{
options.DocInclusionPredicate((documentName, apiDescription) =>
{
var actionApiVersionModel = apiDescription.ActionDescriptor.GetApiVersionModel();
var apiExplorerSettingsAttribute = (ApiExplorerSettingsAttribute)apiDescription.ActionDescriptor.EndpointMetadata.First(x => x.GetType().Equals(typeof(ApiExplorerSettingsAttribute)));
if (actionApiVersionModel == null)
{
return true;
}
if (actionApiVersionModel.DeclaredApiVersions.Any())
{
return actionApiVersionModel.DeclaredApiVersions.Any(v =>
$"{apiExplorerSettingsAttribute.GroupName}v{v}" == documentName);
}
return actionApiVersionModel.ImplementedApiVersions.Any(v =>
$"{apiExplorerSettingsAttribute.GroupName}v{v}" == documentName);
});
var apiVersionDescriptionProvider = services.BuildServiceProvider().GetService<IApiVersionDescriptionProvider>();
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
options.SwaggerDoc($"Orders{description.GroupName}", new OpenApiInfo
{
Title = "Example Service",
Description = "Example Service -- Backend Service Project",
Version = description.ApiVersion.ToString(),
});
options.SwaggerDoc($"Payments{description.GroupName}", new OpenApiInfo
{
Title = "Example Service",
Description = "Example Service -- Backend Service Project",
Version = description.ApiVersion.ToString(),
});
// Orders & Payments are the groupNames, above ex: out put will be : swagger/OrdersV1.0/swagger.json ( if you select Orders from the swagger definition dropdown)
// If you select Payments => output : swagger/PaymentsV1.0/swagger.json
}
//var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(
swaggerOptions =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
swaggerOptions.SwaggerEndpoint($"{swagger/Orders{description.GroupName}/swagger.json",$"Orders {description.GroupName.ToUpperInvariant()}" );
swaggerOptions.SwaggerEndpoint($"{swagger/Payments{description.GroupName}/swagger.json", $"Payments {description.GroupName.ToUpperInvariant()}");
}
});
控制器 //V1 // 示例控制器 1:
[ApiVersion("1.0")]
[ApiController]
[ApiExplorerSettings(GroupName = "Orders")]
[Route("api/v{version:apiVersion}/[controller]")]
public class OrdersController : ControllerBase
// 示例控制器 2:
[ApiVersion("1.0")]
[ApiController]
[ApiExplorerSettings(GroupName = "Payments")]
[Route("api/v{version:apiVersion}/[controller]")]
public class PaymentsController : ControllerBase