Microsoft.AspNetCore.OpenApi 入门
Microsoft.AspNetCore.OpenApi
包为 ASP.NET Core 中的 OpenAPI 文档生成提供内置支持。 包:
- 与本机 AoT 兼容。
- 利用
System.Text.Json
提供的 JSON 架构支持。 - 提供用于修改生成的文档的转换器 API。
- 支持在单个应用中管理多个 OpenAPI 文档。
包安装
安装 Microsoft.AspNetCore.OpenApi
包:
从程序包管理器控制台运行以下命令:
Install-Package Microsoft.AspNetCore.OpenApi -IncludePrerelease
配置 OpenAPI 文档生成
下面的代码:
- 添加 OpenAPI 服务。
- 启用终结点以查看 JSON 格式的 OpenAPI 文档。
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/", () => "Hello world!");
app.Run();
启动应用并导航到 https://localhost:<port>/openapi/v1.json
以查看生成的 OpenAPI 文档。
文档名称的重要性
应用中的每个 OpenAPI 文档都具有唯一的名称。 注册的默认文档名称为 v1
。
builder.Services.AddOpenApi(); // Document name is v1
可通过将名称作为参数传递到 AddOpenApi
调用来修改文档名称。
builder.Services.AddOpenApi("internal"); // Document name is internal
文档名称在 OpenAPI 实现中的多个位置显示。
提取生成的 OpenAPI 文档时,文档名称作为请求中的 documentName
参数提供。 以下请求将解析 v1
和 internal
文档。
GET http://localhost:5000/openapi/v1.json
GET http://localhost:5000/openapi/internal.json
用于自定义 OpenAPI 文档生成的选项
以下部分演示如何自定义 OpenAPI 文档生成。
自定义所生成文档的 OpenAPI 版本
默认情况下,OpenAPI 文档生成将创建一个符合 3.0 版 OpenAPI 规范的文档。 以下代码演示了如何修改 OpenAPI 文档的默认版本:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = OpenApiSpecVersion.OpenApi2_0;
});
自定义 OpenAPI 终结点路由
默认情况下,通过对 MapOpenApi
的调用注册的 OpenAPI 终结点将在 /openapi/{documentName}.json
终结点公开文档。 以下代码演示了如何自定义 OpenAPI 文档注册到的路由:
app.MapOpenApi("/openapi/{documentName}/openapi.json");
注意:可以从终结点路由中移除 documentName
路由参数,但不建议这样做。 从终结点路由中移除 documentName
路由参数时,框架会尝试从查询参数解析文档名称。 在路由或查询中不提供 documentName
可能会导致意外行为。
自定义 OpenAPI 终结点
由于 OpenAPI 文档是通过路由处理程序终结点提供的,因此适用于标准最小终结点的任何自定义项也同样适用于 OpenAPI 终结点。
使用终结点元数据自定义 OpenAPI 终结点
以下列表显示了用于自定义所生成的 OpenAPI 文档的终结点元数据:
- 来自 IEndpointSummaryMetadata 的摘要
- 来自 IEndpointDescriptionMetadata 的说明
- 来自 IAcceptsMetadata 的请求正文
- 来自 IProducesResponseTypeMetadata 的响应信息
- 来自 IEndpointNameMetadata 的操作 ID
- 来自 ITagsMetadata 的 OpenAPI 标记
若要详细了解如何通过修改终结点元数据来自定义所生成的 OpenAPI 文档,请参阅如何在最小 API 应用中使用 OpenAPI。
只有已获授权的用户才能访问 OpenAPI 文档
默认情况下,OpenAPI 终结点不会启用任何授权检查。 但是,可以限制对 OpenAPI 文档的访问。 例如,在以下代码中,只有具有 tester
角色的用户可以访问 OpenAPI 文档:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("ApiTesterPolicy", b => b.RequireRole("tester"));
});
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi()
.RequireAuthorization("ApiTesterPolicy");
app.MapGet("/", () => "Hello world!");
app.Run();
缓存生成的 OpenAPI 文档
每次向 OpenAPI 终结点发送请求时,都会重新生成 OpenAPI 文档。 重新生成使转换器能够将动态应用程序状态合并到其操作中。 例如,使用 HTTP 上下文的详细信息重新生成请求。 在适用的情况下,可以缓存 OpenAPI 文档,以避免针对每个 HTTP 请求执行文档生成管道。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromMinutes(10)));
});
builder.Services.AddOpenApi();
var app = builder.Build();
app.UseOutputCache();
app.MapOpenApi()
.CacheOutput();
app.MapGet("/", () => "Hello world!");
app.Run();
OpenAPI 文档转换器
本部分演示如何使用转换器自定义 OpenAPI 文档。
使用转换器自定义 OpenAPI 文档
转换器提供了一个 API,用于使用用户定义的自定义项修改 OpenAPI 文档。 转换器适用于以下方案:
- 向文档中的所有操作添加参数。
- 修改参数或操作的说明。
- 将顶级信息添加到 OpenAPI 文档。
转换器分为两类:
- 文档转换器有权访问整个 OpenAPI 文档。 这些可用于对文档进行全局修改。
- 操作转换器适用于每个单独的操作。 每个单独的操作都是路径和 HTTP 方法的组合。 这些可用于修改终结点上的参数或响应。
转换器可通过对 OpenApiOptions
对象的 UseTransformer
调用针对文档进行注册。 以下代码片段演示了将转换器注册到文档的不同方法:
- 使用委托注册文档转换器。
- 使用
IOpenApiDocumentTransformer
的实例注册文档转换器。 - 使用已激活 DI 的
IOpenApiDocumentTransformer
注册文档转换器。 - 使用委托注册操作转换器。
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi(options =>
{
options.UseTransformer((document, context, cancellationToken)
=> Task.CompletedTask);
options.UseTransformer(new MyDocumentTransformer());
options.UseTransformer<MyDocumentTransformer>();
options.UseOperationTransformer((operation, context, cancellationToken)
=> Task.CompletedTask);
});
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/", () => "Hello world!");
app.Run();
转换器的执行顺序
转换器根据注册按先进先出的顺序执行。 在下面的代码片段中,文档转换器有权访问操作转换器所做的修改:
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi(options =>
{
options.UseOperationTransformer((operation, context, cancellationToken)
=> Task.CompletedTask);
options.UseTransformer((document, context, cancellationToken)
=> Task.CompletedTask);
});
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/", () => "Hello world!");
app.Run();
使用文档转换器
文档转换器有权访问上下文对象,其中包括:
- 正在修改的文档的名称。
- 与该文档关联的
ApiDescriptionGroups
列表。 - 文档生成中使用的
IServiceProvider
。
文档转换器还可以转换已生成的 OpenAPI 文档。 以下示例演示一个文档转换器,该转换器向 OpenAPI 文档添加有关 API 的一些信息。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi(options =>
{
options.UseTransformer((document, context, cancellationToken) =>
{
document.Info = new()
{
Title = "Checkout API",
Version = "v1",
Description = "API for processing checkouts from cart."
};
return Task.CompletedTask;
});
});
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/", () => "Hello world!");
app.Run();
服务激活的文档转换器可以利用 DI 中的实例来修改应用。 以下示例演示了使用身份验证层中的 IAuthenticationSchemeProvider
服务的文档转换器。 它会检查应用中是否注册了任何与 JWT 持有者相关的方案,并将其添加到 OpenAPI 文档的顶层:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddOpenApi(options =>
{
options.UseTransformer<BearerSecuritySchemeTransformer>();
});
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/", () => "Hello world!");
app.Run();
internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
{
var requirements = new Dictionary<string, OpenApiSecurityScheme>
{
["Bearer"] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer", // "bearer" refers to the header name here
In = ParameterLocation.Header,
BearerFormat = "Json Web Token"
}
};
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = requirements;
}
}
}
文档转换器对于与之关联的文档实例是唯一的。 在下面的示例中,转换器:
- 向
internal
文档注册与身份验证相关的要求。 - 不修改
public
文档。
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddOpenApi("internal", options =>
{
options.UseTransformer<BearerSecuritySchemeTransformer>();
});
builder.Services.AddOpenApi("public");
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/world", () => "Hello world!")
.WithGroupName("internal");
app.MapGet("/", () => "Hello universe!")
.WithGroupName("public");
app.Run();
internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
{
// Add the security scheme at the document level
var requirements = new Dictionary<string, OpenApiSecurityScheme>
{
["Bearer"] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer", // "bearer" refers to the header name here
In = ParameterLocation.Header,
BearerFormat = "Json Web Token"
}
};
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = requirements;
// Apply it as a requirement for all operations
foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
{
operation.Value.Security.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] = Array.Empty<string>()
});
}
}
}
}
使用操作转换器
操作是 OpenAPI 文档中 HTTP 路径和方法的唯一组合。 当修改符合以下情况时,操作转换器非常有用:
- 应该对应用中的每个终结点进行,或
- 有条件地应用于某些路由。
操作转换器有权访问上下文对象,其中包括:
- 操作所属的文档的名称。
- 与该操作关联的
ApiDescription
。 - 文档生成中使用的
IServiceProvider
。
例如,以下操作转换器将 500
添加为文档中所有操作支持的响应状态代码。
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddOpenApi(options =>
{
options.UseOperationTransformer((operation, context, cancellationToken) =>
{
operation.Responses.Add("500", new OpenApiResponse { Description = "Internal server error" });
return Task.CompletedTask;
});
});
var app = builder.Build();
app.MapOpenApi();
app.MapGet("/", () => "Hello world!");
app.Run();
使用生成的 OpenAPI 文档
OpenAPI 文档可插入现有工具的广泛生态系统,用于测试、文档编制和本地开发。
使用 Swagger UI 进行本地临时测试
默认情况下,Microsoft.AspNetCore.OpenApi
包不附带用于直观显示 OpenAPI 文档或与之交互的内置支持。 用于可视化或与 OpenAPI 文档交互的常用工具包括 Swagger UI 和 ReDoc。 Swagger UI 和 ReDoc 可以通过多种方式集成到应用中。 Visual Studio 和 VS Code 等编辑器提供了针对 OpenAPI 文档进行测试的扩展和内置体验。
Swashbuckle.AspNetCore.SwaggerUi
包提供了一组 Swagger UI 的 Web 资产,供在应用中使用。 此包可用于呈现生成的文档的 UI。 若要对此进行配置,请安装 Swashbuckle.AspNetCore.SwaggerUi
包。
使用对先前注册的 OpenAPI 路由的引用来启用 swagger-ui 中间件。 若要限制信息泄漏和安全漏洞问题,请仅在开发环境中启用 Swagger UI。
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
if (app.Environment.IsDevelopment())
{
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "v1");
});
}
app.MapGet("/", () => "Hello world!");
app.Run();
使用 Scalar 交互式 API 文档
Scalar 是 OpenAPI 的开源交互式文档 UI。 Scalar 可与 ASP.NET Core 提供的 OpenAPI 终结点集成。 若要配置 Scalar,请安装 Scalar.AspNetCore
包。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
if (app.Environment.IsDevelopment())
{
app.MapScalarApiReference();
}
app.MapGet("/", () => "Hello world!");
app.Run();
使用 Spectral 对生成的 OpenAPI 文档进行 Lint 分析
Spectral 是一个开源的 OpenAPI 文档 Linter。 Spectral 可整合到应用生成中,以验证生成的 OpenAPI 文档的质量。 根据包安装说明安装 Spectral。
若要利用 Spectral,请安装 Microsoft.Extensions.ApiDescription.Server
包以启用生成时 OpenAPI 文档生成。
通过在应用的 .csproj
文件中设置以下属性,启用生成时文档生成:
<PropertyGroup>
<OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)</OpenApiDocumentsDirectory>
<OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
</PropertyGroup>
运行 dotnet build
以生成文档。
dotnet build
创建具有以下内容的 .spectral.yml
文件。
extends: ["spectral:oas"]
对生成的文件运行 spectral lint
。
spectral lint WebMinOpenApi.json
...
The output shows any issues with the OpenAPI document.
```output
1:1 warning oas3-api-servers OpenAPI "servers" must be present and non-empty array.
3:10 warning info-contact Info object must have "contact" object. info
3:10 warning info-description Info "description" must be present and non-empty string. info
9:13 warning operation-description Operation "description" must be present and non-empty string. paths./.get
9:13 warning operation-operationId Operation must have "operationId". paths./.get
✖ 5 problems (0 errors, 5 warnings, 0 infos, 0 hints)
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈