使用 ASP.NET Core 构建 Web API:11 API 文档

本章涵盖

  • 识别 Web API 的潜在用户
  • 在 Swagger 和 Swashbuckle 中应用 API 文档最佳实践
  • 使用可扩展标记语言 (XML) 文档和虚张声势注释
  • 使用 Swashbuckle 的筛选器管道自定义 swagger.json 文件

在第1章中,当我们尝试定义应用程序编程接口(API)时,我们将其称为软件平台,它公开了不同计算机程序可以通过交换数据进行交互的工具和服务。从这个定义开始,我们可以说 API(包括 Web API)的目的是创建一个公共场所,让独立且一般不相关的系统可以使用普遍接受的标准进行会面、问候和通信。这些“参与者”大多是由其他开发人员实现的计算机程序,例如网站、移动应用程序和微服务。出于这个缘由,无论谁承担设计、创建和发布 Web API 的任务,都必须承认一种新型用户的存在和需求:第三方开发人员,这将我们带到本章的主题。

在现代软件开发中,记录接口、中间件、服务或任何旨在达到目的的手段的产品不再被视为一种选择:只要我们想增加或加速其采用,它就是一项设计要求。这也是让感兴趣的第三方能够充分理解我们工作价值的最快方法。近年来,这方面变得如此重大,以至于它有利于定义一个新的设计领域:开发人员体验(DX),即从开发人员角度看的用户体验。通过思考DX,我将在本章专门确定API文档的最佳实践,并展示我们如何借助 ASP.NET Core提供的许多工具将它们付诸实践。

11.1 网页应用接口潜在受众

产品的技术文档只有在满足阅读者的需求和期望时才有用。出于这个缘由,第一要做的是确定我们的 Web API 潜在受众:期望选择和/或使用它的利益相关者。在提到它们时,我一般将它们分为三种主要类型,使用取自建筑俚语的名称。

11.1.1 探矿者

探矿者是充满激情的开发人员和 IT 爱好者,他们愿意尝试我们的 Web API,而除了个人兴趣、知识获取、测试/审查目的等之外,没有迫切的需求。如果我们希望我们的 Web API 成为我们打算向公众发布的通用产品(或其中的一部分),则此组很重大;他们的反馈可能会对开发人员社区产生直接影响,可能会引入承包商和建筑商(分别参见第 11.1.2 节和第 11.1.3 节)。

11.1.2 承包商

承包商是 IT 分析师、解决方案架构师和后端设计人员,他们负责创建产品、解决问题或解决我们的 Web API 可以协助他们处理的潜在挑战。虽然一般情况下,他们不会着手实施,但他们一般充当决策者,由于他们拥有权力、处理预算和/或拥有提议、选择或规定使用哪些组件所需的专业知识(除非他们让构建者选择它们)。

11.1.3 构建器

构建者是选择(或被指示)使用我们的 Web API 来解决特定问题的软件开发人员。他们代表了我们受众中技术性最强的部分,可能很难满足,由于处理我们的 API 是他们工作任务的一部分,而且他们完成工作的时间一般有限。构建者必须学会实际使用我们的 Web API;他们是我之前提到的第三方开发人员。

在阅读了这些描述之后,似乎很明显地认为我们的文档应该关注构建者,他们是我们的 Web API 的最终用户。这个前提是有效的。我们将在本章中讨论的大多数 API 文档最佳实践都将思考这种方法。但我们不应该忘记其他两种受众类型,由于我们项目的成功也可能取决于他们。

11.2 API 文档最佳实践

开发人员是特殊类型的用户。他们善于分析、准确且要求苛刻,特别是如果我们认为他们一般希望使用我们的 API 来实现主要目标:实现需求、解决问题等。每当他们发现自己由于文档编写不佳而无法实现目标时,他们很可能会认为 API 不够好——尽管听起来很残酷,但他们是对的。归根结底,API 的好坏取决于它们的文档,这不可避免地会对采用和可维护性产生巨大影响。

我们所说的良好文档是什么意思,我们如何实现它?没有一个答案在所有情况下都有效。但是一些好的做法可以协助我们找到一种可行的方法来实现我们想要的东西,例如:

  • 采用自动描述工具 – 这样,如果我们忘记更新文档以及 Web API 的源代码,我们的文档就不会过时或过时
  • 描述端点和输入参数 – 以便我们的受众不仅承认它们的存在,而且了解它们的用途以及如何使用它们
  • 描述响应 – 以便我们的听众知道调用每个端点时会发生什么以及如何处理结果
  • 添加请求和响应示例 – 为我们的受众节省大量开发时间
  • 将端点分组到多个部分 – 更好地区分用户的不同作用域、用途和角色
  • 排除保留端点 – 防止用户知道它们的存在和/或尝试调用它们
  • 强调授权要求 – 让我们的受众区分可公开访问的操作和仅限于授权用户的操作
  • 自定义文档上下文 – 例如选择适当的名称、图标和元数据以协助用户查找所需信息

以下部分将详细介绍这些概念,并展示如何在我们的 MyBGList Web API 中实现它们。

11.2.1 采用自动描述工具

如果我们想让第三方开发人员满意,我们必须确保我们的 API 文档始终更新。没有什么比处理缺少的规范、不存在的操作或端点、错误的参数等更令人沮丧的了。过时的文档会让我们的用户认为我们的 API 坏了,即使它不是。

注意记录不佳(或错误)的 Web API 在技术上已损坏,由于第三方没有机会看到它按预期工作。这就是我们最初声明API文档是设计要求,而不是选项或附加组件的缘由。即使对于预计仅由内部开发人员使用的内部 API 也是如此,由于缺乏适当的文档最终会影响新员工、潜在合作伙伴、维护任务、移交流程、外包商等。

对于 RESTful API 来说,自动化文档过程的需求尤其强烈,由于 REST 架构标准没有为此目的提供标准化的机制、模式或参考。这是Open API(以前称为Swagger)成功的主要缘由,Open API(以前称为Swagger)是SmartBear Software于2011年发布的自动化API文档的开源规范,旨在解决这个问题。

我们从第2章开始就知道Swagger/OpenAPI,由于Visual Studio的 ASP.NET Core Web API模板(我们用来创建MyBGList项目)包括Swashbuckle的服务和中间件,这是一组用于在Core中实现OpenAPI的服务,中间件和工具 ASP.NET。我们还体验了它的自动发现和描述功能,它为我们提供了一个代码生成的 OpenAPI 3.0 描述文件 (swagger.json) 和一个基于 Web 的交互式 API 客户端 (SwaggerUI),我们用它来测试我们的端点。由于我们已经在使用Swashbuckle,所以我们可以说我们已经准备好了。但是,在以下部分中,我们将扩展其功能以满足我们的需求。

11.2.2 描述端点和输入参数

如果我们查看我们的 SwaggerUI 主仪表板,我们会发现我们当前的“文档”仅包含端点及其输入变量的列表,而没有对每个方法的作用进行单一描述。我们的受众必须从名称中推断端点的使用,以及每个请求标头和/或输入参数的用途,这不是展示、评估或推广我们工作的最佳方式。

注意如果我们的 API 遵循使用 HTTP 谓词来标识操作类型的 RESTful 良好实践,它将提供有关每个端点使用的其他有用提示 – 至少对具有所需专业知识的用户而言。

相反,我们应该采用标准化的方式来为每个端点及其输入变量创建简洁而相关的描述。这种做法不仅可以节省建筑商的时间,还可以让探矿者和承包商更好地掌握API的工作方式及其功能。Swashbuckle 提供了两种向端点和输入参数添加自定义描述的方法:

  • 使用由 .NET 编译器从标准三斜杠、XML 格式注释自动生成的可扩展标记语言 (XML) 文档文件,我们可以将其添加到 C# 类中。
  • 使用 [SwaggerOperation] 数据属性,该属性由可选的 Swashbuckle 提供。AspNetCore.Annotations NuGet 包。

每种技术都有优点和缺点。在下一节中,我们将了解如何实现这两种技术。

11.2.3 添加 XML 文档支持

如果我们已经使用 C# 提供的三斜杠语法在源代码中添加了注释,则 XML 文档方法可能很有用、方便且快速实现。我说的是一个简洁的 C# 功能,它允许开发人员通过编写由三斜杠指示的特殊注释字段来创建代码级文档。某些集成开发环境 (IDE)(如 Visual Studio)也使用此功能,这些环境自动生成 XML 元素来描述各种代码部分,例如<摘要>(用于方法)、<param>(用于输入参数)和<returns>(用于返回值)。

注意有关 C# XML 文档注释的其他信息,请查看 http://mng.bz/qdy6。有关支持的 XML 标记的完整参考,请参阅 http://mng.bz/7187

学习如何使用此功能的最佳方法是将其付诸实践。打开
/Controllers/AccountController.cs 文件,找到 Register 操作方法,将光标置于其上方(及其所有属性),然后在其上方键入斜杠 (/) 字符三次。添加第三个斜杠后,Visual Studio 应生成以下 XML 注释样板:

/// <summary>
///
/// </summary>
/// <param></param>
/// <returns></returns>
[HttpPost]
[ResponseCache(CacheProfileName = "NoCache")]
public async Task<ActionResult> Register(RegisterDTO input)

请注意,自动生成的 XML 结构标识操作方法的 RegisterDTO 输入参数的名称。目前我们有了样板,让我们填充它。以下是我们如何记录帐户控制器的注册终结点:

/// <summary>
/// Registers a new user.
/// </summary>
/// <param>A DTO containing the user data.</param>
/// <returns>A 201 - Created Status Code in case of success.</returns>

之后,向下滚动到登录操作方法,然后执行一样的操作。以下是我们可以用来记录它的合适描述:

/// <summary>
/// Performs a user login.
/// </summary>
/// <param>A DTO containing the user's credentials.</param>
/// <returns>The Bearer Token (in JWT format).</returns>

保存并关闭帐户控制器。接下来,告知编译器使用我们添加的 XML 注释以及代码中存在的任何其他此类注释来生成 XML 文档文件。

生成 XML 文档文件

要启用此功能,我们需要更新 MyBGList 项目的配置文件。在“解决方案资源管理器”窗口中右键单击项目的根节点,然后从上下文菜单中选择“编辑项目文件”选项以打开 MyBGList.csproj 文件。接下来,在文件底部的 <ItemGroup> 块下方添加以下代码,我们在第 10 章中添加了包含 protobuf 文件:

// ... existing code
 
<ItemGroup>
  <Protobuf Include="gRPC/grpc.proto" />
</ItemGroup>
 
<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
 
// ... existing code

目前,每当我们构建项目时,编译器都会生成 XML 文档文件。

克服 CS1591 警告

我们在前面的代码中使用的 <NoWarn> 元素将禁止显示 CS1591 警告,GenerateDocumentationFile 开关将为任何公共类型和成员引发该警告,而无需三斜杠注释。我们选择在我们的示例项目中全局关闭它们,由于我们不需要该提议,但是如果我们想确保我们注释/记录所有内容,那么保持它可能会很有用。

有关生成文档文件开关的详细信息,请参阅 http://mng.bz/mJdn。

我们需要做的最后一件事是配置 Swashbuckle 以获取 XML 文档文件的内容。

配置虚张声势扣

要读取我们项目的XML文档文件,Swashbuckle需要知道它的完整路径和文件名,与我们项目的名称(带有.xml扩展名)相对应。我们可以使用 Reflection(一种 C# 技术,允许我们在运行时检索类型的元数据),而不是手动编写它,而是手动编写它。这种编程方法一般更可取,由于它比使用文本字符串更能确保代码可维护性,因此我们将选择它。打开 Program.cs 文件,找到 AddSwaggerGen() 方法,并在其配置块中添加以下代码(粗体新行):

using System.Reflection;                                           ❶
 
// ... existing code
 
builder.Services.AddSwaggerGen(options =>
{
    var xmlFilename =                                              ❷
        $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.IncludeXmlComments(System.IO.Path.Combine(             ❸
        AppContext.BaseDirectory, xmlFilename));
 
    // ... existing code

必需的命名空间

构建 XML 文档文件名

组装 XML 文档文件完整路径

请注意,在此代码中,我们使用反射生成与项目名称匹配的 XML 文件名,然后使用它来构造 XML 文件的完整路径。接下来,我们将测试我们所做的工作,看看它是否有效。

测试 XML 文档

在调试模式下运行项目,并查看 SwaggerUI 主仪表板,我们应该在其中看到我们在三斜杠注释中使用的一样描述性字符串(图 11.1)。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.1 Swashbuckle 获取并在 SwaggerUI 中使用的 XML 文档

请注意,摘要紧跟在终结点定义之后。说明显示在端点的可展开面板中。

评估 XML 文档的优缺点

能够自动将所有代码级注释转换为 API 文档,使我们能够用一块石头杀死两只鸟。如果我们习惯于编写注释来描述我们的类和方法(开发人员的良好做法),我们可以重用大量工作。此外,这种方法对内部开发人员特别有用,由于他们可以直接从源代码中读取我们的API文档,甚至不必查看swagger.json文件和/或SwaggerUI。

但这种显着的好处很容易成为不利的一面。例如,如果我们想将内部源代码文档(针对内部开发人员)与公共 API 文档(针对第三方开发人员/最终用户)分开,我们可能会发现这种方法是有限的,更不用说涉及非自愿数据泄露的潜在风险。代码级注释一般被大多数开发人员视为机密,他们可能会使用它们来跟踪内部注释、警告、已知问题/错误、漏洞和其他不应向公众发布的严格保留的数据。为了克服这样的问题,我们可以思考使用 [SwaggerOperation] 数据属性替代,它可以更好地分离内部注释和 API 文档之间的关注点,以及我们可能想要使用的一些简洁的附加功能。

11.2.4 使用虚张声势的注释

除了 XML 文档之外,Swashbuckle 还提供了另一种基于属性的功能,用于向我们的 Web API 端点添加自定义描述。此功能由一个名为
Swashbuckle.AspNetCore.Annotations 的可选模块处理,该模块随专用 NuGet 包一起提供。在以下部分中,我们将学习如何安装和使用它。

安装 NuGet 包

与往常一样,若要安装 Swashbuckle 注释的 NuGet 包,我们可以使用 Visual Studio 的 NuGet 图形用户界面 (GUI)、包管理器控制台窗口或 .NET 命令行界面 (CLI)。下面是在 .NET CLI 中安装它们的命令:

dotnet add package Swashbuckle.AspNetCore.Annotations --version 6.4.0

安装后,我们将能够使用一些数据注释属性来增强我们的 API 文档。我们将从 [SwaggerOperation] 开始,它允许我们为控制器的操作方法以及最小 API 方法设置自定义摘要、描述和/或标签。

使用 [SwaggerOperation] 属性

由于我们的帐户控制器的操作方法已经通过XML记录,因此这次我们将使用BoardGamesController。打开 /Controllers/ BoardGamesController.cs 文件,并将属性添加到四个现有的操作方法中,如清单 11.1 所示(新行以粗体显示)。

清单 11.1 /控制器/棋盘游戏控制器.cs 文件: 添加注释

using Swashbuckle.AspNetCore.Annotations;                                ❶
 
// ... existing code
 
[HttpGet(Name = "GetBoardGames")]
[ResponseCache(CacheProfileName = "Any-60")]
[SwaggerOperation(                                                       ❷
    Summary = "Get a list of board games.",                              ❸
    Description = "Retrieves a list of board games " +                   ❹
    "with custom paging, sorting, and filtering rules.")]
public async Task<RestDTO<BoardGame[]>> Get(
 
// ... existing code
 
[HttpGet("{id}")]
[ResponseCache(CacheProfileName = "Any-60")]
[SwaggerOperation(                                                       ❷
    Summary = "Get a single board game.",                                ❸
    Description = "Retrieves a single board game with the given Id.")]   ❹
public async Task<RestDTO<BoardGame?>> Get(int id)
 
// ... existing code
 
[Authorize(Roles = RoleNames.Moderator)]
[HttpPost(Name = "UpdateBoardGame")]
[ResponseCache(CacheProfileName = "NoCache")]
[SwaggerOperation(                                                       ❷
    Summary = "Updates a board game.",                                   ❸
    Description = "Updates the board game's data.")]                     ❹
public async Task<RestDTO<BoardGame?>> Post(BoardGameDTO model)
 
// ... existing code
 
[Authorize(Roles = RoleNames.Administrator)]
[HttpDelete(Name = "DeleteBoardGame")]
[ResponseCache(CacheProfileName = "NoCache")]
[SwaggerOperation(                                                       ❷
    Summary = "Deletes a board game.",                                   ❸
    Description = "Deletes a board game from the database.")]            ❹
public async Task<RestDTO<BoardGame?>> Delete(int id)
 
// ... existing code

必需的命名空间

[招摇操作]属性

添加端点摘要

添加端点描述

目前我们知道如何使用 Swashbuckle 注释来描述我们的操作,让我们对它们的输入参数做同样的事情。

使用 [SwaggerParameter] 属性

要设置输入参数的描述,我们可以使用 [SwaggerParameter] 属性,该属性对应于 XML 文档的 <param> 标记的 Swashbuckle 注释。但是,尽管 XML 标记必须在方法级别定义,然后通过 name 属性绑定到其相应的参数,但 [SwaggerParameter] 注释必须在它要描述的参数之上定义。

要了解它是如何工作的,让我们实现它。在保持 BoardGamesController.cs 文件打开的同时,找到 Get() 方法,并通过以下方式将 [SwaggerParameter] 添加到现有输入参数(新行以粗体显示):

public async Task<RestDTO<BoardGame[]>> Get(
    [FromQuery]
    [SwaggerParameter("A DTO object that can be used " +     ❶
        "to customize the data-retrieval parameters.")]
    RequestDTO<BoardGameDTO> input)

添加 [招摇参数]

目前,描述性属性已经设置好,我们需要通过更新我们的 Swagger 配置来全局启用虚张声势注释功能。

启用批注

若要启用虚张声势注释,请打开 Program.cs 文件,并将以下配置设置添加到现有的 AddSwaggerGen() 方法中:

builder.Services.AddSwaggerGen(options =>
{
    options.EnableAnnotations();     ❶
 
    // ... existing code

启用虚张声势注释功能

添加最低 API 支持

[SwaggerOperation] 属性以及整个 Swashbuckle 注释功能甚至可以使用最小 API 方法。让我们将其中一些方法添加到循环中。保持 Program.cs 文件打开状态,向下滚动到我们在第 9 章中实现的三个最小 API 方法,以测试 ASP.NET Core 授权功能。然后将 [SwaggerOperation] 属性添加到它们,如以下列表所示(粗体新行)。

清单 11.2 程序.cs文件:向最小 API 方法添加注释

using Swashbuckle.AspNetCore.Annotations;                           ❶
 
// ... existing code
 
app.MapGet("/auth/test/1",
[Authorize]
[EnableCors("AnyOrigin")]
[SwaggerOperation(                                                  ❷
    Summary = "Auth test #1 (authenticated users).",                ❸
    Description = "Returns 200 - OK if called by " +                ❹
    "an authenticated user regardless of its role(s).")]
[ResponseCache(NoStore = true)] () =>
 
// ... existing code
 
app.MapGet("/auth/test/2",
[Authorize(Roles = RoleNames.Moderator)]
[EnableCors("AnyOrigin")]
[SwaggerOperation(                                                  ❷
    Summary = "Auth test #2 (Moderator role).",                     ❸
    Description = "Returns 200 - OK status code if called by " +    ❹
    "an authenticated user assigned to the Moderator role.")]
[ResponseCache(NoStore = true)] () =>
 
// ... existing code
 
app.MapGet("/auth/test/3",
[Authorize(Roles = RoleNames.Administrator)]
[EnableCors("AnyOrigin")]
[SwaggerOperation(                                                  ❷
    Summary = "Auth test #3 (Administrator role).",                 ❸
    Description = "Returns 200 - OK if called by " +                ❹
    "an authenticated user assigned to the Administrator role.")]
[ResponseCache(NoStore = true)] () =>
 
// ... existing code

必需的命名空间

[招摇操作]属性

添加端点摘要

添加端点描述

目前,我们已准备好测试我们所做的工作。

测试注释

要测试我们的新注释,请在调试模式下运行我们的项目,并查看 SwaggerUI 主仪表板,我们应该能够在其中看到它们(图 11.2)。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.2 通过 [SwaggerOperation] 属性添加的 OpenAPI 注释

如我们所见,总体结果与使用 XML 文档方法获得的结果超级类似。但是,我们可以记录每种技术的内容之间存在一些显着差异。例如,XML文档允许我们描述示例(使用<example>元素),Swashbuckle注释目前不支持这些示例。同时,Swashbuckle 注释功能可以使用自定义模式过滤器进行扩展,以支持 Swagger/OpenAPI 规范中提到的几乎任何文档选项。在以下各节中,我们将以互补的方式使用这两种方法,以充分利用它们。

11.2.5 描述响应

用于终结点和输入参数的一样描述性方法也应应用于我们的 Web API 响应。此方法不仅适用于返回的 JavaScript 对象表明法 (JSON) 数据,还适用于 HTTP 状态代码(应始终根据其含义使用)和相关响应标头(如果有)。

同样,为了描述我们的响应,我们可以使用<响应> XML 文档标签或专用的 [SwaggerResponse] Swashbuckle 注释属性。在以下部分中,我们将采用这两种方法。

使用 XML 文档

正如我们之前对 <param> 标签所做的那样,它可以多次用于描述每个输入参数,我们可以为该方法返回的任何 HTTP 状态代码创建一个 <response> 标签。每个 XML <响应>标记都需要一个代码属性(以确定它所描述的响应的 HTTP 状态代码)和一个包含实际说明的基于文本的值。若要对其进行测试,请再次打开
/Controllers/AccountController.cs 文件,并将以下 <response> 标记追加到 Register 方法的现有 XML 文档注释块(粗体新行):

/// <summary>
/// Registers a new user.
/// </summary>
/// <param>A DTO containing the user data.</param>
/// <returns>A 201 - Created Status Code in case of success.</returns>
/// <response code="201">User has been registered</response>
/// <response code="400">Invalid data</response>
/// <response code="500">An error occurred</response>

HTTP 状态代码 201 说明

HTTP 状态代码 400 说明

HTTP 状态代码 500 说明

接下来,向下滚动到 Login 方法,并在其中附加以下 <response> 标记:

/// <summary>
/// Performs a user login.
/// </summary>
/// <param>A DTO containing the user's credentials.</param>
/// <returns>The Bearer Token (in JWT format).</returns>
/// <response code="200">User has been logged in</response>
/// <response code="400">Login failed (bad request)</response>
/// <response code="401">Login failed (unauthorized)</response>

HTTP 状态代码 200 说明

HTTP 状态代码 400 说明

HTTP 状态代码 401 说明

若要测试我们所做的工作,请在调试模式下启动项目,访问 SwaggerUI 主仪表板,然后展开“帐户/注册”和“帐户/登录”终结点。如果我们正确执行了所有操作,我们应该看到我们的响应描述,如图 11.3 所示。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.3 /帐户/登录端点的响应说明

目前我们知道了如何使用 XML 文档注释来获取此结果,让我们看看如何使用 [SwaggerResponse] 数据注释属性实现一样的操作。

使用虚张声势扣批注

[SwaggerResponse] 属性与其对应的 <response> XML 标记对应项一样,可以多次添加到同一方法,以描述受影响的方法可能发送回客户端的所有结果、HTTP 状态代码和响应类型。此外,它还需要两个主要参数:

  • 要描述的响应的 HTTP 状态代码
  • 我们要显示的描述

学习如何使用它的最好方法是看到它的实际效果。打开 Program.cs 文件,向下滚动到 /auth/test/1 最小 API 端点,然后添加新的 [SwaggerResponse] 属性以按以下方式描述其唯一响应:

app.MapGet("/auth/test/1",
    [Authorize]
    [EnableCors("AnyOrigin")]
    [SwaggerOperation(
        Summary = "Auth test #1 (authenticated users).",
        Description = "Returns 200 - OK if called by " +
        "an authenticated user regardless of its role(s).")]
    [SwaggerResponse(StatusCodes.Status200OK,
        "Authorized")]                                  ❶
    [SwaggerResponse(StatusCodes.Status401Unauthorized,
        "Not authorized")]                              ❷

HTTP 状态代码 201 说明

HTTP 状态代码 401 说明

请注意,我们使用了 Microsoft.AspNetCore 提供的 StatusCodes 枚举。Http 命名空间,它允许我们使用强类型方法指定 HTTP 状态代码。

注意使用基于属性的方法的一个优点是,它为我们提供了 C# 和 ASP.NET Core 功能提供的所有好处,包括但不限于强类型成员。例如,我们可以通过使用 Core 的内置本地化支持(由于篇幅缘由,本书中没有介绍 ASP.NET 为不同的语言和/或文化指定不同的描述。

若要测试该属性,请在调试模式下启动项目,访问 SwaggerUI 仪表板,并在 /auth/test/1 终结点的 SwaggerUI 面板的“响应”部分中检查是否存在上述说明(图 11.4)。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.4 /auth/test/1 端点的响应说明

不错。但是,我们的大多数端点不仅发出 HTTP 状态代码;如果请求成功,它们还会返回具有明确定义的预定结构的 JSON 对象。向我们的 API 用户描述这些返回类型,让他们知道会发生什么,这不是很好吗?为了实现这样的目标,我们需要在这些描述中添加一些样本。在下一节中,我们将看到如何操作。

11.2.6 添加请求和响应示例

理想情况下,每个 API 操作都应包含一个请求和响应示例,以便用户了解每个操作的预期工作方式。正如我们已经知道的,我们心爱的 SwaggerUI 负责请求部分的任务;每当我们使用它时,它都会显示一个示例值选项卡,其中包含 JSON 格式的示例输入数据传输对象 (DTO),如图 11.5 所示。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.5 /帐户/注册终结点的响应示例

“示例值”选项卡的右侧是一个简洁的“架构”选项卡,其中显示了对象的架构和许多有用的信息,例如每个字段的最大大小、可为空性和基础类型。遗憾的是,此自动功能并不总是适用于 JSON 响应类型,需要一些手动干预。

注意有时,SwaggerUI 会设法自动检测(并显示示例)响应类型。例如,如果我们展开 GET /BoardGames 端点的 SwaggerUI 面板,则 RestDTO<BoardGame> 对象将正确显示在响应部分中。遗憾的是,当该方法具有多种返回类型时,此方便的功能一般无法自动检测其中的大多数返回类型。下一节中介绍的方法将处理这些方案。

让我们看看如何告知 SwaggerUI 随时显示响应示例。[ProducesResponseType] 属性附带 Microsoft.AspNetCore.Mvc 命名空间,不是 Swashbuckle 的一部分。但是,由于我们将组件配置为思考注释,因此 SwaggerUI 将使用它来确定每个方法的响应类型并采取相应的行动。

与 [ProducesResponseType] 属性一起使用的主要参数是响应类型和方法返回的状态代码。同样,由于终结点可以返回不同的响应类型和状态代码,因此可以多次将其添加到每个方法中。我们已经知道 SwaggerUI 无法自动检测 /Account/Register 和 /Account/Login 端点的返回类型,这使得它们成为此属性的完美候选者。

打开 /控制器/帐户控制器.cs 文件,并找到注册操作方法。然后在现有属性下方,在方法声明之前添加以下属性(粗体新行):

[HttpPost]
[ResponseCache(CacheProfileName = "NoCache")]
[ProducesResponseType(typeof(string), 201)]                      ❶
[ProducesResponseType(typeof(BadRequestObjectResult), 400)]      ❷
[ProducesResponseType(typeof(ProblemDetails), 500)]              ❸

HTTP 状态代码 201 说明

HTTP 状态代码 400 说明

HTTP 状态代码 500 说明

使用以下属性对 Login 操作方法执行一样的操作:

[HttpPost]
[ResponseCache(CacheProfileName = "NoCache")]
[ProducesResponseType(typeof(string), 200)]                   ❶
[ProducesResponseType(typeof(BadRequestObjectResult), 400)]   ❷
[ProducesResponseType(typeof(ProblemDetails), 401)]           ❸

HTTP 状态代码 200 说明

HTTP 状态代码 400 说明

HTTP 状态代码 401 说明

若要测试我们所做的工作,请在调试模式下启动项目,并查看 SwaggerUI 中“/帐户/注册”和“/帐户/登录终结点”面板的“响应”部分,以确保它们看起来像图 11.6 中的那些。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.6 /帐户/注册返回类型的 JSON 示例

图 11.6 中描述的屏幕截图已被裁剪,由于在 HTTP 状态代码 400 的情况下返回的 BadRequestObjectResult 的 JSON 表明形式很长。但是这个数字应该让我们了解我们做了什么。目前我们已经知道如何强制 SwaggerUI 提供响应类型的示例,我们已准备好掌握另一种良好做法:终结点分组。

11.2.7 将端点分组为多个部分

如果 Web API 具有大量终结点,则将它们分组到与其角色/用途对应的部分中可能很有用。在我们的方案中,明智的做法是将身份验证终结点、在棋盘游戏实体上运行的终结点等分组。我们可以说我们已经这样做了,由于我们为每个组都使用了一个控制器,遵循 ASP.NET Core默认行为。正如我们从第 1 章开始就知道的那样,ASP.NET Core 控制器允许我们对一组具有共同主题、含义或记录类型的操作方法进行分组。我们在 MyBGList 方案中采用了此约定,对与棋盘游戏相关的终结点使用 BoardGamesController,对基于域的终结点使用 DomainsController 等。

这种方法由我们当前的 Open API 实现自动实施。如果我们查看 SwaggerUI 仪表板,我们会看到由与同一控制器相关的操作方法处理的 API 端点被分组,如图 11.7 所示。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.7 控制器处理的终结点的 SwaggerUI 组名称

我们可以猜到,这些组是由Swashbuckle自动生成的。此技巧是通过 tags 属性将控制器的名称添加到 swagger.json 文件中来执行的,该属性旨在处理分组任务。

提示有关 Swagger 的标签属性的其他信息,请查看 http://mng.bz/5178

要进行检查,请单击 SwaggerUI 主标题下方的超链接打开 swagger.json 文件,或导航到 https://localhost:
40443/swagger/v1/swagger.json。/Account/Register 终结点的 tags 属性位于文件开头附近:

{
  "openapi": "3.0.1",
  "info": {
    "title": "MyBGList",
    "version": "1.0"
  },
  "paths": {
    "/Account/Register": {   ❶
      "post": {
        "tags": [            ❷
          "Account"
        ],

/帐户/注册端点说明

“帐户”标签取自控制者的名称

遗憾的是,此自动行为不适用于最小 API 方法,由于它们不属于控制器。Swashbuckle唯一能做的就是将它们全部列出在一个通用组中,并带有应用程序的名称(在我们的场景中是MyBGList),如图11.8所示。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.8 最小 API 处理的终结点的通用组

这种回退行为的结果还不错。但是,由于我们当前的最小 API 端点处理不同的相关任务集,因此我们可能希望找到一种更好的方法来对它们进行分组。

如果我们想改善 Swashbuckle 的默认标记行为,我们可以使用 [SwaggerOperation] 属性提供的 Tags 属性来覆盖它。让我们测试一下。假设我们要将三个端点分组,从 /auth/ 段开始,在一个名为“Auth”的新 SwaggerUI 部分中。打开程序.cs文件;找到这些方法;并对其现有的 [SwaggerOperation] 属性进行以下更改,从 /auth/test/1 端点开始(新行以粗体显示):

app.MapGet("/auth/test/1",
    [Authorize]
    [EnableCors("AnyOrigin")]
    [SwaggerOperation(
        Tags = new[] { "Auth" },    ❶
        Summary = "Auth test #1 (authenticated users).",
        Description = "Returns 200 - OK if called by " +
        "an authenticated user regardless of its role(s).")]

添加标签属性

对 /auth/test/2 和 /auth/test/3 方法执行一样的操作,然后在调试模式下运行项目以查看新的身份验证组(图 11.9)。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.9 与授权相关的终结点的新身份验证组

我们可以使用一样的技术来覆盖属于控制器的操作方法的 Swashbuckle 默认行为。每当 Tags 参数与自定义值一起存在时,Swashbuckle 将始终使用它来填充 swagger.json 文件,而不是回退到控制器或操作方法的名称。

注意如果我们想自定义端点的组名称而不是使用控制器名称,则此覆盖功能会很方便。但是,请务必记住,这种级别的自定义违反了 ASP.NET Core 强制执行的最重大的开发最佳实践之一:配置设计范例约定,该范例旨在限制开发人员需要做出的决策数量以及源代码量,而不会失去灵活性。出于这个缘由,我强烈提议遵守控制器的 ASP.NET 核心分组和标记约定,将 Tags 属性自定义做法保留为最小 API 方法和有限数量的异常。

11.2.8 排除保留端点

ApiExplorer 服务 Swashbuckle 用于在我们项目的源代码中自动查找所有控制器的操作方法和最小 API 方法,并在 swagger.json 文件中描述它们,在大多数情况下是一个很棒的功能。但我们可能想要隐藏一些我们不想向观众展示的方法(或整个控制器)。

在我们当前的情况下,这种情况可能适用于 SeedController,其中包含几个旨在由管理员调用和知道的方法。从 swagger.json 文件中排除这些操作可能是明智的,这也将把它们从 SwaggerUI 中删除。

为了实现这个结果,我们可以使用 [ApiExplorerSettings] 属性,其中包含一个有用的 IgnoreApi 属性。此属性可应用于任何控制器、操作方法或最小 API 方法。让我们用它来从 swagger.json 文件中排除我们的 SeedController。打开
/Controllers/SeedController.cs 文件,并按以下方式将该属性应用于类声明:

[Authorize(Roles = RoleNames.Administrator)]
[ApiExplorerSettings(IgnoreApi = true)]       ❶
[Route("[controller]/[action]")]
[ApiController]
public class SeedController : ControllerBase

从 swagger.json 文件中排除控制器

要测试我们所做的工作,请在调试模式下运行项目;导航到 招摇UI主仪表板;并确认我们之前访问该页面时存在的整个种子部分不再可见。

警告请务必了解,IgnoreApi = true 设置只会阻止控制器及其操作方法包含在 swagger.json 文件中;它不会阻止用户调用(并可能执行)它。这就是为什么我们还通过使用第 9 章中的 [Authorize] 属性将其限制为管理员。

到目前为止,我们已经学习了如何使用 XML 文档或数据注释属性处理各个方法来配置 swagger.json 文件的内容和生成的 SwaggerUI 布局。在下一节中,我们将了解如何基于Swashbuckle过滤器的使用,使用更加结构化和聚焦的方法执行这些类型的更改。

11.3 基于过滤器的招摇自定义

正如我们从第6章中知道的,Swashbuckle公开了一个方便的过滤器管道,它与swagger.json文件生成过程挂钩,允许我们创建和添加自己的过滤器,以根据需要自定义文件的内容。要实现过滤器,我们需要做的就是扩展 Swashbuckle 提供的内置接口之一,每个接口都提供了一个方便的 Apply 方法来自定义自动生成的文件。以下是Swashbuckle提供的过滤器接口的完整列表:

  • IDocumentFilter – 自定义整个 swagger.json 文件
  • IOperationFilter – 自定义操作/端点
  • IParameterFilter – 自定义操作的查询字符串输入参数
  • IRequestBodyFilter – 自定义操作的请求正文输入参数
  • ISchemaFilter – 自定义输入参数的默认方案

我们在第6章中使用了这个功能,当时我们添加了SortColumnFilter和SortOrderFilter(扩展IParameterFilter接口),为SwaggerUI提供了一些基于正则表达式的模式来验证一些输入参数。Swashbuckle 使用这些过滤器,我们在 /Swagger/ 文件夹中实现了这些过滤器,然后将其添加到 Program.cs 文件中的 Swashbuckle 过滤器管道中,有选择地将模式 JSON 键添加到使用 [SortColumnValidator] 和 [SortOrderValidator] 自定义属性修饰的所有参数。我们所做的是一个简单而完美的过滤器管道如何工作的例子。

在本节中,我们将学习如何使用 Swashbuckle 提供的其他过滤器接口来进一步配置自动生成的 swagger.json 文件,从而相应地更新 SwaggerUI。与往常一样,我们假设我们被要求实现一些可信的新功能请求。

11.3.1 强调授权要求

在第 9 章中,当我们学习如何使用 [Authorize] 属性时,我们在现有的 Swagger 配置设置中添加了安全定义和安全要求。我们这样做是为了使“授权”按钮显示在 SwaggerUI 中,目前允许我们设置持有者令牌并测试受授权限制的终结点。但是这个添加有一个我们当时故意忽略的次要效果:它还在我们的所有端点旁边添加了一个奇怪的挂锁图标,如图 11.10 所示。

使用 ASP.NET Core 构建 Web API:11 API 文档

图11.10 招摇UI中的挂锁图标

单击这些图标时,将显示“授权”弹出窗口,就像我们在第 9 章中多次使用的页面右上角附近的“授权”按钮一样。但是,无论终结点的授权要求如何,挂锁图标始终显示为打开状态,这不是我们预期的行为。理想情况下,我们希望挂锁图标仅出目前需要某种授权的端点旁边。下一节将介绍如何实现此结果。

在深入研究源代码之前,让我们看看挂锁图标功能在后台是如何工作的。如果端点具有某种安全要求(换句话说,如果需要某种级别的授权),则 SwaggerUI 会自动呈现这些图标。此信息取自 swagger.json 文件,该文件为这些端点分配安全属性:

"security": [{
        "Bearer": [ ]
    }]

在第 9 章中,当我们配置 Swagger 以支持基于令牌的授权机制时,我们使用项目程序.cs文件中的专用配置选项向 swagger.json 文件生成器服务添加了全局安全要求:

// ...existing code
 
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecurityScheme
        {
            Name = "Bearer",
            In = ParameterLocation.Header,
            Reference = new OpenApiReference
            {
                Type=ReferenceType.SecurityScheme,
                Id="Bearer"
            }
        },
        new string[]{}
    }
});
 
// ...existing code

由于这一全局安全要求,我们在所有端点上都设置了安全属性,由于它们被认为受到基于令牌的授权方案的保护,即使它们不是。为了修补此行为,我们需要将该全局要求替换为特定规则,该规则将仅针对受此类方案限制的方法触发。

最有效的方法是使用 IOperationFilter 接口创建自定义筛选器,该接口可以扩展 swagger.json 生成器服务,以便为受影响的操作提供其他信息(或修改现有/默认信息)。在我们的方案中,我们需要一个筛选器,该筛选器可以设置当前分配给所有操作的一样安全要求,但仅适用于应用了 [Authorize] 属性的操作。若要实现此要求,请在 /Swagger/ 根级文件夹中创建一个新的 AuthRequirementsFilter.cs 类文件,并使用以下列表中的源代码填充其内容。

清单 11.3
/Swagger/AuthRequirementsFilter.cs 文件

using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
 
namespace MyBGList.Swagger
{
    internal class AuthRequirementFilter : IOperationFilter
    {
        public void Apply(
            OpenApiOperation operation,
            OperationFilterContext context)
        {
            if (!context.ApiDescription                                 ❶
                .ActionDescriptor
                .EndpointMetadata
                .OfType<AuthorizeAttribute>()
                .Any())
                return;                                                 ❷
 
            operation.Security = new List<OpenApiSecurityRequirement>   ❸
            {
                new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Name = "Bearer",
                            In = ParameterLocation.Header,
                            Reference = new OpenApiReference
                            {
                                Type=ReferenceType.SecurityScheme,
                                Id="Bearer"
                            }
                        },
                        new string[]{}
                    }
                }
            };
        }
    }
}

检查 [授权] 属性

如果不存在,则不执行任何操作

如果存在,则确保操作安全

如我们所见,我们的新操作过滤器在内部执行当前在 Program.cs 文件中完成的一样任务。唯一的区别是它跳过了没有 [Authorize] 属性的操作,由于我们不希望它们在 swagger.json 文件(或挂锁图标)中记录任何安全要求。

目前我们有了 AuthRequirementsFilter,我们需要更新 Swagger 生成器配置选项以使用它,而不是我们当前拥有的全局缩放要求。打开程序.cs文件;向下滚动到 AddSwaggerGen 方法;并将现有的 AddSecurityRequirement 语句替换为新的 AddOperationFilter 语句,如下面的代码清单所示。(前面的代码行被注释掉;新的代码行以粗体显示。

清单 11.4 程序.cs文件: AddSwaggerGen 配置更新

using MyBGList.Swagger;                                           ❶
 
// ... existing code...
 
//options.AddSecurityRequirement(new OpenApiSecurityRequirement   ❷
//{
//    {
//        new OpenApiSecurityScheme
//        {
//            Name = "Bearer",
//            In = ParameterLocation.Header,
//            Reference = new OpenApiReference
//            {
//                Type=ReferenceType.SecurityScheme,
//                Id="Bearer"
//            }
//        },
//        new string[]{}
//    }
//});
options.OperationFilter<AuthRequirementFilter>();                 ❸
 
// ... existing code...

必需的命名空间

要删除的先前代码

要添加的新代码

提示在本章的 GitHub 存储库中,我注释掉了以前的代码行,而不是删除它们。

为了测试我们所做的工作,我们可以在调试模式下启动项目,并再次查看以前具有挂锁图标的一样端点(图 11.11)。正如我们所看到的,挂锁图标对于可公开访问的端点已经消失,但对于那些需要某种授权的端点来说,挂锁图标依旧存在。我们的自定义IOperationFilter允许我们做我们想做的事情。

使用 ASP.NET Core 构建 Web API:11 API 文档

图11.11 SwaggerUI中挂锁图标的新行为

11.3.2 更改应用程序标题

假设我们要在 SwaggerUI 中更改应用程序的标题,该标题当前设置为 MyBGList,根据 Swashbuckle 的默认行为,与 ASP.NET Core 项目同名。如果我们查看 swagger.json 文件,我们可以看到托管该值的 JSON 属性称为 title,它是在文档级别设置的父信息属性的一部分:

{
  "openapi": "3.0.1",
  "info": {
    "title": "MyBGList Web API",
    "version": "1.0"
  },

这意味着,如果我们想覆盖它,我们需要创建一个自定义过滤器,允许我们自定义 swagger.json 文件的文档级参数。实现目标的最有效方法是创建自定义 DocumentFilter(通过扩展 IDocumentFilter 接口)并将其添加到筛选器管道中。在 /Swagger/ 根级文件夹中创建一个新的 CustomDocumentFilter.cs 文件,并使用以下清单的内容填充该文件。

清单 11.5
/Swagger/CustomDocumentFilter.cs 文件

using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
 
namespace MyBGList.Swagger
{
    internal class CustomDocumentFilter : IDocumentFilter
    {
        public void Apply(
            OpenApiDocument swaggerDoc,
            DocumentFilterContext context)
        {
            swaggerDoc.Info.Title = "MyBGList Web API";   ❶
        }
    }
}

设置自定义标题

然后,通过按以下方式更新 Program.cs 文件,将文件挂接到 Swashbuckle 的过滤器管道(新行以粗体显示):

options.OperationFilter<AuthRequirementFilter>();   ❶
options.DocumentFilter<CustomDocumentFilter>();     ❷

现有过滤器

新过滤器

要测试我们所做的工作,请在调试模式下启动项目,并查看 SwaggerUI 仪表板的新标题(图 11.12)。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.12 SwaggerUI 标题随自定义文档筛选器更改

不错。让我们看看我们可以用IRequestBodyFilter接口做什么。

11.3.3 为密码添加警告文本

假设我们希望在用户需要向我们的 Web API 发送密码时为其设置自定义警告文本。通过查看我们当前的端点,我们可以很容易地确定,目前,这样的警告只会影响账户控制器的注册和登录方法。通过思考这一实际,我们可以使用 XML 文档注释(第 11.2.3 节)或 [SwaggerOperation] 属性(第 11.2.4 节)将此消息插入到操作的“摘要”或“说明”属性中,正如我们之前所学习的那样。或者,我们可以通过使用 <param> XML 标记或 [SwaggerParameter] 属性在参数级别工作。

这两种方法都有一个不平凡的缺点。如果我们将来添加接受密码的端点,我们还必须在那里重复 XML 标记或数据注释属性,这意味着复制大量代码 — 除非我们忘记这样做,由于这样的方法很容易出错。

为了克服这些问题,最好找到一种方法来聚焦这种行为,方法是创建一个新的过滤器并将其添加到Swashbuckle的管道中。我们需要确定在可用过滤器接口中扩展哪个过滤器接口。理想情况下,IRequestBodyFilter 接口将是一个不错的选择,思考到我们希望定位名称等于“密码”的特定参数,该参数目前(并且可能总是)随 POST 请求一起出现。让我们继续这种方法。在 /Swagger/ 根文件夹中创建一个新的 PasswordRequestFilter.cs 文件,并使用以下清单中的代码填充该文件。

清单 11.6
/Swagger/PasswordRequestFilter.cs 文件

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
 
namespace MyBGList.Swagger
{
    internal class PasswordRequestFilter : IRequestBodyFilter
    {
        public void Apply(
            OpenApiRequestBody requestBody,
            RequestBodyFilterContext context)
        {
            var fieldName = "password";                         ❶
 
            if (context.BodyParameterDescription.Name
                .Equals(fieldName,
                    StringComparison.OrdinalIgnoreCase)         ❷
                || context.BodyParameterDescription.Type
                .GetProperties().Any(p => p.Name
                    .Equals(fieldName,
                        StringComparison.OrdinalIgnoreCase)))   ❸
            {
                requestBody.Description =
                    "IMPORTANT: be sure to always use a strong password " +
                    "and store it in a secure location!";
            }
        }
    }
}

输入参数名称

名称检查(基元类型)

属性检查(复杂型)

通过查看此代码,我们检查输入参数名称是否等于“password”(对于基元类型)或包含具有该名称的属性(对于复杂类型,例如 DTO)。目前我们有了过滤器,我们需要通过以下方式在 Swashbuckle 的过滤器管道中注册它,在我们之前添加的 AuthRequirementsFilter 和 CustomDocumentFilter 下面:

options.OperationFilter<AuthRequirementFilter>();     ❶
options.DocumentFilter<CustomDocumentFilter>();       ❶
options.RequestBodyFilter<PasswordRequestFilter>();   ❷

现有过滤器

新过滤器

与往常一样,我们可以通过在调试模式下执行项目并检查 SwaggerUI 中的预期更改来测试我们所做的工作(图 11.13)。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.13 密码请求筛选器添加的新描述

这些变化似乎奏效了。由于这种方法,我们的密码警告消息将涵盖我们的两个现有终结点以及在其请求正文中接受密码参数的任何未来终结点。

注意如果我们想将覆盖范围扩展到查询字符串参数,我们需要添加另一个扩展 IParameterFilter 接口并执行一样工作的筛选器,然后使用 ParameterFilter 协助程序方法将其注册到 Program.cs 文件中。

目前要完成我们的过滤器概述,剩下要做的就是 ISchemaFilter 接口。

11.3.4 添加自定义键/值对

让我们再看一下我们在第 6 章中实现的 SortColumnFilter 和 SortOrderFilter 类。扩展 IParameterFilter 接口是个好主意,由于我们只需要处理来自查询字符串的一些特定输入参数。换句话说,我们希望将模式键添加到 swagger.json 文件中这些参数的 JSON 模式中,从用于标识它们的一样数据注释属性 [SortColumnAttribute] 或 [SortOrderAttribute] 中获取值。

假设我们要扩展该方法以实现一个新的过滤器,该过滤器能够将任意 JSON 键(和值)添加到任何属性,无论是请求参数、响应参数还是其他任何内容。在本节中,我们将通过实现以下内容来实现这一目标:

  • 自定义数据注释属性,这将允许我们为任何属性设置一个或多个自定义 JSON 键和值对
  • 一个自定义 SchemaFilter,它扩展了 ISchemaFilter 接口,将这些键和值对添加到应用了这些数据注释属性的所有参数、响应和属性中

ISchemaFilter 接口是处理此任务的完美选择,由于它专门设计用于对 Swashbuckle 的 SwaggerGen 服务生成的 JSON 模式进行后修改,用于控制器操作和最小 API 方法公开的每个输入和输出参数以及复杂类型。目前我们已经选择了我们的路线,让我们把它付诸实践。

实现自定义键值属性

在 Visual Studio 的“解决方案资源管理器”面板中,右键单击 MyBGList 项目根目录中的 /Attributes/ 文件夹,并添加新的
CustomKeyValueAttribute.cs 类文件,其中包含两个字符串属性:Key 和 Value。下面的清单提供了新类的源代码。

11.7 自定义键值属性

namespace MyBGList.Attributes
{
    [AttributeUsage(
        AttributeTargets.Property | AttributeTargets.Parameter,
        AllowMultiple = true)]
    public class CustomKeyValueAttribute : Attribute
    {
        public CustomKeyValueAttribute(string? key, string? value)
        {
            Key = key;
            Value = value;
        }
 
        public string? Key { get; set; }
 
        public string? Value { get; set; }
    }
}

请注意,我们已经使用 [AttributeUsage] 属性装饰了我们的新类,它允许我们指定属性的使用。我们这样做有两个重大缘由:

  • 若要允许将属性应用于属性和参数,请使用 AttributeTargets 枚举。
  • 允许多次应用该属性,由于 AllowMultiple 属性设置为 true。此设置是必需的,由于我们希望有机会将多个 [SwaggerSchema] 属性(从而设置多个自定义键/值对)应用于单个属性或参数。

目前我们有了属性,我们准备实现将处理它的筛选器。

实现自定义键值筛选器

在 /Swagger/ 文件夹中添加新的 CustomKeyValueFilter.cs 类文件。新类必须实现 ISchemaFilter 接口及其 Apply 方法,我们将在其中处理 [CustomKeyValue] 属性查找和 JSON 键/值对插入过程。以下清单显示了如何操作。

清单 11.8 自定义键值过滤器

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
 
namespace MyBGList.Attributes
{
    public class CustomKeyValueFilter : ISchemaFilter
    {
        public void Apply(
            OpenApiSchema schema,
            SchemaFilterContext context)
        {
            var caProvider = context.MemberInfo
                ?? context.ParameterInfo
                as IcustomAttributeProvider;            ❶
 
            var attributes = caProvider?
                .GetCustomAttributes(true)
                .OfType<CustomKeyValueAttribute>();     ❷
 
            if (attributes != null)                     ❸
            {
                foreach (var attribute in attributes)
                {
                    schema.Extensions.Add(
                        attribute.Key,
                        new OpenApiString(attribute.Value)
                        );
                }
            }
        }
    }
}

确定我们是在处理属性还是参数

检查参数是否具有属性

如果存在一个或多个属性,则相应地采取行动

此代码应易于理解。我们将使用语言集成查询 (LINQ) 检查 ISchemaFilter 接口提供的上下文,以确定我们的属性或参数是否应用了一个或多个 [CustomKeyValue] 属性并采取相应的操作。我们目前需要做的就是将新的过滤器添加到 Swashbuckle 的过滤器管道中。与往常一样,我们可以通过以下方式更新程序.cs文件:

options.OperationFilter<AuthRequirementFilter>();    ❶
options.DocumentFilter<CustomDocumentFilter>();      ❶
options.RequestBodyFilter<PasswordRequestFilter>();  ❶
options.SchemaFilter<CustomKeyValueFilter>();        ❷

现有过滤器

新过滤器

目前我们的两个类已经准备就绪,并且过滤器已经注册,我们可以通过将 [CustomKeyValue] 属性应用于现有 DTO 之一的属性来测试 [CustomKeyValue] 属性。让我们选择帐户控制器的登录操作方法使用的登录DTO。打开 /DTO/LoginDTO.cs 文件,并按以下方式将其中几个属性应用于现有 UserName 属性:

[Required]
[MaxLength(255)]
[CustomKeyValue("x-test-1", "value 1")]   ❶
[CustomKeyValue("x-test-2", "value 2")]   ❶
public string? UserName { get; set; }

第一个自定义键值属性

接下来,在调试模式下运行项目,访问 SwaggerUI 仪表板,然后单击主标题下方的 swagger.json 文件链接(图 11.14),在新选项卡中打开它。

使用 ASP.NET Core 构建 Web API:11 API 文档

图 11.14 swagger.json 文件 URL

使用浏览器的搜索功能在 swagger.json 文件中查找“x-test-”字符串。如果我们正确执行了所有操作,我们应该在 LoginDTO 的用户名属性的 JSON 架构中看到此字符串的两个条目,如以下列表所示。

清单 11.9 swagger.json 文件 (登录DTO 模式)

      "LoginDTO": {
        "required": [
          "password",
          "userName"
        ],
        "type": "object",
        "properties": {
          "userName": {
            "maxLength": 255,
            "minLength": 1,
            "type": "string",
            "x-test-1": "value 1",   ❶
            "x-test-2": "value 2"    ❶
          },
          "password": {
            "minLength": 1,
            "type": "string"
          }
        }

自定义键/值对

目前为止,一切都好。让我们执行另一个测试,以确保一样的逻辑适用于基元类型的标准 GET 参数。打开
/Controllers/BoardGamesController.cs 文件,向下滚动到 Get 操作方法,接受 int 类型的单个 id 参数,然后按以下方式向该参数添加 [CustomKeyValue] 属性:

[HttpGet("{id}")]
[ResponseCache(CacheProfileName = "Any-60")]
[SwaggerOperation(
    Summary = "Get a single board game.",
    Description = "Retrieves a single board game with the given Id.")]
public async Task<RestDTO<BoardGame?>> Get(
    [CustomKeyValue("x-test-3", "value 3")]    ❶
    int id
    )

添加新的 [自定义键值] 属性

接下来,在调试模式下运行项目,像我们之前一样访问 swagger.json 文件内容,并再次检查其中是否存在“x-test-”字符串。这一次,我们应该找到三个条目,最后一个是我们添加的条目(请参阅下面的列表)。

清单 11.10 swagger.json 文件 (/BoardGames/{id} 端点模式)

    "/BoardGames/{id}": {
      "get": {
        "tags": [
          "BoardGames"
        ],
        "summary": "Get a single board game.",
        "description": "Retrieves a single board game with the given Id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32",
              "x-test-3": "value 3"    ❶
            }
          }
        ],

自定义键/值对

我们的自定义键/值功能似乎运行良好。最后一个任务结束了我们了解 Swashbuckle 的过滤器管道和 API 文档概述的旅程。目前唯一要做的就是学习将我们的 Web API 项目部署到生产环境中,这是第 12 章的主题。

11.4 练习

是时候用我们的产品所有者给出的一系列新的假设任务分配来挑战自己了。与往常一样,处理这些任务将极大地协助我们记住和记住本章中涵盖的概念和学到的技术。

注意练习的解决方案可在 GitHub 的 /Chapter_11/Exercises/ 文件夹中找到。若要测试它们,请将 MyBGList 项目中的相关文件替换为该文件夹中的文件,然后运行应用。

11.4.1 使用 XML 文档

使用 XML 文档方法,按以下方式描述 GET /Domains 终结点:

  • 摘要 – 获取域列表
  • 描述 – 检索具有自定义分页、排序和过滤规则的域列表
  • 参数 – 可用于自定义某些检索参数的 DTO 对象
  • 返回 – 包含域列表的 RestDTO 对象

提示可以使用 <备注> XML 元素添加说明。

11.4.2 使用虚张声势的注释

使用 Swashbuckle 注释方法,按以下方式描述 GET /Mechanics 终结点:

  • 摘要 – 获取机制列表
  • 描述 – 检索具有自定义分页、排序和过滤规则的机制列表
  • 参数 – 可用于自定义某些检索参数的 DTO 对象
  • 返回 – 包含机制列表的 RestDTO 对象

11.4.3 排除某些端点

使用 [ApiExplorerSettings] 属性从 swagger.json 文件中隐藏以下端点:

  • 帖子/域
  • 删除/域

然后,确保这些终结点也从 SwaggerUI 仪表板中排除。

11.4.4 添加自定义过滤器

扩展 IRequestBodyFilter 接口以实现新的 UsernameRequestFilter,该筛选器将向名称等于“用户名”的任何输入参数添加以下说明。然后在 Swashbuckle 的过滤器管道中注册新过滤器,并通过检查 POST 帐户/登录和 POST 帐户/注册端点使用的 sername 参数在 SwaggerUI 仪表板中对其进行测试。

警告请务必记住您的用户名,由于您需要它来执行登录!

11.4.5 添加自定义键/值对

使用 [自定义键值] 属性将以下键/值对添加到 DELETE 机制端点的现有 id 参数:

  • 键:x-测试-4,值:值 4
  • 键:x-测试-5,值:值 5

然后检查 swagger.json 文件中终结点的 JSON 架构中是否存在新属性。

总结

  • 编写良好的文档可以大大增加或加速 Web API 的采用。
    • 因此,请务必确定 API 文档最佳实践并学习使用 ASP.NET Core 内置和第三方工具遵循这些最佳实践。
  • 确定我们 Web API 的潜在受众(期望选择和/或使用它的利益相关者)可以协助我们编写引人注目的文档。理想情况下,我们需要满足
    • 渴望尝试我们所做的事情的早期采用者(探矿者)。
    • 旨在评估我们工作的 IT 解决方案架构师(承包商)。
    • 将被要求实现我们 Web API 端点的软件开发人员(构建器)。
  • 在不忘记其他两个受众群体(探矿者和承包商)的情况下关注建筑商的需求几乎总是要走的路。
  • 开发人员善于分析、准确且要求苛刻。为了满足他们的期望,请务必遵守 IT 行业广泛采用的一些众所周知的文档最佳实践,包括
    • 采用自动描述工具。
    • 描述端点、输入参数和响应。
    • 提供请求和响应示例。
    • 将终结点分组为多个部分。
    • 强调授权要求。
    • 自定义文档上下文。
  • Swagger/OpenAPI 框架提供了一种标准化的方法来记录和描述 API,使用每个人都能理解的通用语言。
    • 借助Swashbuckle,我们可以使用Swagger为我们的Web API创建文档:一组服务,中间件和工具,允许我们在遵循之前确定的最佳实践的同时 ASP.NET Core中实现OpenAPI规范。
  • Swashbuckle 公开了一组方便的数据属性,以及一个强劲的过滤器管道,可用于对自动生成的 swagger.json 文件进行后修改,从而自定义 API 文档以满足我们的需求。
    • Swashbuckle 的功能使我们能够改善操作、输入参数和输出参数的描述,以及向现有 JSON 模式添加自定义键/值对。
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容