January 23rd, 2025

.NET 9 中的 OpenAPI 文档生成

Eddie Chen
Partner Technical Advisor

本文翻译自Mike KistlerOpenAPI document generation in .NET 9

.NET 9 中的 ASP.NET Core 通过引入全新的对OpenAPI 文档生成功能的内置支持,简化了为 API 端点创建 OpenAPI 文档的过程。这项新功能旨在简化开发工作流程,并改善 OpenAPI 定义在 ASP.NET 应用中的集成。 OpenAPI 的广泛使用催生了丰富的工具和服务生态系统,它们能够帮助您更高效地构建、测试和记录 API。例如,Swagger UIKiota 客户端库生成器Redoc 等,当然还有许多其他工具。 

为什么选择 OpenAPI? 

OpenAPI 是定义和记录 HTTP API 的强大工具。它提供了一种标准化方式来描述 API 的端点、请求和响应格式、身份验证方案以及其他重要细节。这种标准化使开发人员能够更轻松地了解和与API进行交互,从而促进更好的协作并构建更强大的应用程序。 

此外,许多大型语言模型(LLMs)已在 OpenAPI 文档上进行了训练,使其能够自动生成代码、测试用例和其他工件。通过为您的 API 生成 OpenAPI 文档,您可以利用这些 LLM 来加速开发流程。 

.NET 9 中的新功能? 

在 .NET 9 中,我们引入了对 OpenAPI 文档生成功能的内置支持,为 .NET 开发人员提供了更集成、更流畅的体验。此功能可用于Minimal API 和基于控制器的应用程序。以下是一些关键亮点: 

  • 支持在运行时生成 OpenAPI 文档,并通过应用程序上的端点访问它们,或在构建时生成文档。 
  • 用于将元数据添加到 API 方法和数据的属性和扩展方法 。 
  • 支持“转换器”API,允许以多种方式修改生成的文档。 
  • 支持从单个应用程序生成多个 OpenAPI 文档。 
  • 与Minimal API 结合使用时,兼容原生 AoT(Ahead-of-Time)编译。 

如何入门 

在 .NET 9 中使用新的 OpenAPI 文档生成功能非常简单。以下是一个帮助您入门的快速指南。 

更新到 .NET 9 

确保您的项目使用的是本月初发布的 .NET 9。您可以从  .NET 官方网站 下载最新版本。 

如果您想为现有项目添加 OpenAPI 支持,则需要将项目的目标框架更新为 .NET 9。有关详细的迁移指南,请参阅 ASP.NET Core 文档中的相关内容。 

启用 OpenAPI 支持 

如果您正在创建一个新项目,OpenAPI 支持已经内置在 NET9. Webapi 模板中。 

要在现有项目中启用 OpenAPI 文档支持,您只需添加 Microsoft.AspNetCore.OpenApi 包,并在主应用程序文件中添加几行代码。 

您可以通过命令 dotnet add package 添加该包: 

dotnet add package Microsoft.AspNetCore.OpenApi

接着,您需要在 Program.cs 文件中将 OpenAPI 服务添加到 WebApplicationBuilder: 

builder.Services.AddOpenApi();

OpenAPI 功能提供了各种配置选项,例如设置文档标题、版本和其他元数据。您可以在 ASP.NET Core 文档中找到有关这些选项的更多信息。 

然后,在应用程序中添加端点,通过 MapOpenApi 扩展方法为 OpenAPI 文档提供服务,如下所示: 

app.MapOpenApi();

现在,您可以运行您的应用程序,并在 /openapi/v1.json 端点访问生成的 OpenAPI 文档。 

您将在该端点看到一个包含路径、操作和模式的 OpenAPI 文档,这些内容是基于您的应用程序代码生成的,但可能不包括描述和示例等重要细节。要获取这些元素,您需要按照下一节中的说明添加元数据。 

添加 OpenAPI 元数据 

描述、标签、示例和其他元数据都可以被添加到 API 方法和数据中,以赋予生成的 OpenAPI 文档含义。您可以使用属性或扩展方法添加此元数据。 

您可以使用 WithSummaryWithDescription 扩展方法为应用程序中的每个端点添加摘要和描述: 

app.MapGet("/hello", () => "Hello, World!")
    .WithSummary("Get a greeting")
    .WithDescription("This endpoint returns a friendly greeting.");

端点的摘要和描述非常重要,因为它们告诉用户(以及大语言模型LLM)可以通过您的 API 完成哪些操作。 

您可能还希望将相关的端点在文档中进行分组,通常可以通过标签来实现。您可以使用 WithTag 扩展方法为端点添加标签: 

app.MapGet("/hello", () => "Hello, World!")
    .WithTag("Greetings");

当端点具有参数时,为每个参数添加描述是很重要的,它用于解释其含义以及该参数如何被端点使用。您可以使用 [Description] 属性为参数添加描述: 

app.MapGet("/hello/{name}",
(
    [Description("The name of the person to greet.")] string name
) => $"Hello, {name}!")
    .WithSummary("Get a personalized greeting")
    .WithDescription("This endpoint returns a personalized greeting.")
    .WithTag("Greetings");

您还可以使用 [Description] 属性为数据模型中的属性添加描述: 

public record Person
{
    [Description("The person's name.")]
    public string Name { get; init; }

    [Description("The person's age.")]
    public int Age { get; init; }
}

还有许多其他元数据属性用于描述参数和属性,包括 [MaxLength](最大长度)、[Range](范围)、[RegularExpression] (正则表达式)和 [DefaultValue](默认值)。请注意,在基于控制器的应用程序中,这些属性会触发在模型绑定期间执行的验证,但在Minimal API 中,它们仅用于文档生成。 

请参阅文档中的包括 OpenAPI 元数据主题,了解有关将元数据添加到 API 方法和数据的更多信息。 

自定义文档 

ASP.NET 还提供了一种使用“转换器”自定义生成的 OpenAPI 文档的方法,转换器可以对整个文档、操作或架构进行操作。转换器是实现 IOpenApiDocumentTransformerIOpenApiOperationTransformerIOpenApiSchemaTransformer 接口的类。每个接口都有一个异步方法,用于接收要转换的文档、操作或架构以及提供其他信息的上下文对象。传递给转换器的 OpenAPI 文档、操作或架构是使用 Microsoft.OpenApi.Models 命名空间中类型的强类型对象。该方法通过修改它接收到的对象来“原地”执行转换。 

转换器通过 AddOpenApi 调用的 configureOptions 委托参数添加,并且可以指定为类的实例、以 DI 激活的类或委托方法。 

builder.Services.AddOpenApi(options => 
{ 
    // 添加文档转换器作为类的实例  
    options.AddDocumentTransformer(new MyDocumentTransformer()); 
    // 以 DI 激活类的形式添加操作转换器  
    options.AddOperationTransformer<MyOperationTransformer>(); 
    // 作为委托方法添加模式转换器  
    options.AddSchemaTransformer((schema, context, cancellationToken) 
                            => Task.CompletedTask); 
}); 

文档转换器的一种用处是修改 OpenAPI 文档中 pathscomponents.schemas 之外的部分。例如,您可以在文档的 info 元素中添加 contact,如下所示: 

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        document.Info.Contact = new OpenApiContact
        {
            Name = "Contoso Support",
            Email = "support@contoso.com"
        };
        return Task.CompletedTask;
    }
});

操作转换器可用于修改文档中的单个操作。操作转换器会针对应用中的每个操作调用,它可以选择是否修改操作。例如,您可以像这样为所有需要授权的操作添加一个安全性要求: 

    options.AddOperationTransformer((operation, context, cancellationToken) =>
    {
        if (context.Description.ActionDescriptor.EndpointMetadata.OfType<IAuthorizeData>().Any())
        {
            operation.Security = [new() { ["Bearer"] = [] }];
        }
        return Task.CompletedTask;
    });

架构转换器可用于修改应用程序的架构。架构描述了操作的请求或响应体。请求或响应体中的复杂属性可能具有其自己的架构。架构转换器可以用来修改任意或所有这些架构。 

需要注意的是,包括架构转换器在内的所有转换器都会在架构被转换为 “$ref” 引用之前被调用——此过程将在下一节中讨论。 

以下示例展示了一个简单的架构转换器,它将任何表示 C# decimal (十进制)值的架构的 format (格式)字段设置为 decimal(十进制): 

    options.AddSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.JsonTypeInfo.Type == typeof(decimal))
        {
            // default schema for decimal is just type: number.  Add format: decimal
            schema.Format = "decimal";
        }
        return Task.CompletedTask;
    });

自定义架构重用 

在所有转换器应用完成后,框架会对文档进行处理,将某些架构转移到 components.schemas 部分,并替换为指向被转移架构的 $ref 引用。这种处理减少了文档的大小,使其更易于阅读。 

这个处理的细节比较复杂,并且可能会在未来的 .NET 版本中发生变化,但总体来说: 

  • 对于 类/记录/结构类型的架构,如果它们在文档中出现多次,则会被替换为指向 components.schemas 中架构的 $ref 引用。 
  • 对于原始类型和标准集合的架构,则保持“内联”。 
  • 对于枚举类型的架构,总是会被替换为指向 components.schemas 中架构的 $ref 引用。 

通常情况下,components.schemas 中的架构名称是类/记录/结构类型的名称,但在某些情况下可能需要使用其他名称。 

ASP.NET Core 允许您通过配置 OpenApiOptionsCreateSchemaReferenceId 属性自定义哪些架构会被替换为指向 components.schemas 中架构的 $ref 引用。此属性是一个委托,它接受 JsonTypeInfo 对象并返回 components.schemas 中应用于该类型的架构的名称。框架提供了此委托的默认实现 OpenApiOptions.CreateDefaultSchemaReferenceId,它使用了类型的名称,但您也可以用自己的实现来代替它。 

举个简单的自定义例子,您可以选择始终内联枚举模式。这可以通过将 CreateSchemaReferenceId 设置为一个委托实现来实现,该委托对枚举类型始终返回 null,而对其他类型则返回默认实现的值。以下代码展示了如何完成此操作: 

builder.Services.AddOpenApi(options =>
{
    // Always inline enum schemas
    options.CreateSchemaReferenceId = (type) => type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type);
});

在构建时生成 OpenAPI 文档 

我想许多 .NET 开发人员都会发现,在构建时生成 OpenAPI 文档的选项是一项非常吸引人的功能。将 OpenAPI 文档的生成作为构建过程的一部分,可以更轻松地与本地开发工作流或 CI 管道中的工具集成。例如,您可以对生成的文档运行 linter (检验程序)以确保其符合组织的标准,或者使用该文档生成客户端代码或测试。 

在构建时生成 OpenAPI 文档非常简单。只需将 Microsoft.Extensions.ApiDescription.Server 包添加到您的项目中。默认情况下,OpenAPI 文档会生成到项目的 obj 目录中,但您可以通过 OpenApiDocumentsDirectory 属性自定义生成文档的位置。例如,要将文档生成到项目的根目录,可以在项目文件中添加以下内容: 

<PropertyGroup>
  <OpenApiDocumentsDirectory>./</OpenApiDocumentsDirectory>
</PropertyGroup>

请注意,构建时生成 OpenAPI 文档的机制是通过启动应用程序的入口点并使用惰性服务器实现来完成的。这允许框架纳入仅在运行时可用的元数据,但在某些构建场景下,可能需要对您的应用程序进行一些更改以确保正常工作。 

有关更多信息,请参阅文档中的构建时生成 OpenAPI主题。 

总结 

.NET 9 中新的 OpenAPI 文档生成功为开发人员创建和维护 ASP.NET 应用程序的 API 文档提供了新的途径。通过将此功能直接集成到 ASP.NET Core 中,开发人员现在可以在构建时或运行时生成 OpenAPI 文档,根据需要自定义它们,并确保它们与代码保持同步。而且,在Minimal API 应用程序中,此功能与原生 AoT 编译完全兼容。 

我们很乐意听到您对此新功能的反馈。请试用并告诉我们您的想法。 

祝您编码愉快! 

Author

Eddie Chen
Partner Technical Advisor

0 comments