教程:生成 REST API 客户端

使用 REST API 的应用程序是一种非常常见的方案。 通常,需要生成应用程序可用于调用 REST API 的客户端代码。 本教程介绍如何使用 MSBuild 在生成过程中自动生成 REST API 客户端。 你将使用 NSwag,该工具为 REST API 生成客户端代码。

GitHub 上 .NET 示例存储库中的 REST API 客户端生成 提供了完整的示例代码。

该示例显示了一个控制台应用,该应用使用公共 Pet Store API,该应用发布 OpenAPI 规范

本教程假定 MSBuild 术语(如任务、目标、属性或运行时)的基本知识;有关必要的背景信息,请参阅 MSBuild 概念文章

如果要在生成过程中运行命令行工具,有两种方法需要考虑。 一种是使用 MSBuild Exec 任务,这使你可以运行命令行工具并指定其参数。 另一种方法是创建自定义任务,该任务派生自 ToolTask,这样可以更好地控制。

先决条件

你应该了解 MSBuild 概念,例如任务、目标和属性。 请参阅 MSBuild 概念

这些示例需要使用 Visual Studio 安装的 MSBuild,但也可以单独安装。 请参阅下载 MSBuild 而不下载 Visual Studio

选项 1:执行任务

Exec 任务 只需调用具有指定参数的指定进程,等待它完成,然后在进程成功完成时返回 true;如果发生错误,false

可以从 MSBuild 使用 NSwag 代码生成;请参阅 NSwag.MSBuild

完整的代码位于 PetReaderExecTaskExample 文件夹中;你可以下载并查看。 在本教程中,你将逐步完成并了解相关概念。

  1. 创建名为 PetReaderExecTaskExample的新控制台应用程序。 使用 .NET 6.0 或更高版本。

  2. 在同一解决方案中创建另一个项目:PetShopRestClient(此解决方案将包含生成的客户端作为库)。 对于此项目,请使用 .NET Standard 2.1。 生成的客户端不会在 .NET Standard 2.0 上进行编译。

  3. PetReaderExecTaskExample 项目中,并向 PetShopRestClient 项目添加项目依赖项。

  4. PetShopRestClient 项目中,包括以下 NuGet 包:

    • Nswag.MSBuild,它允许从 MSBuild 访问代码生成器
    • Newtonsoft.Json,编译生成的客户端时需要
    • System.ComponentModel.Annotations,编译生成的客户端时需要
  5. PetShopRestClient 项目中,为代码生成添加文件夹(命名为 PetShopRestClient),并删除自动生成的 Class1.cs

  6. 在项目的根目录中创建名为 petshop-openapi-spec.json 的文本文件。 从此处 复制 OpenAPI 规范 并将其保存在文件中。 最好复制规范的快照,而不是在生成过程中联机读取。 你总是想要一个一致且可复现的构建,它只依赖于输入。 直接使用 API 可能会将今天工作的生成转换为明天从同一源失败的生成。 保存在 petshop-openapi-spec.json 上的快照将确保我们即便在规范更改后,仍然可以构建一个版本。

  7. 接下来,修改 PetShopRestClient.csproj 并添加 MSBuild 目标,以在构建过程中生成客户端。

    首先,添加一些对客户端生成有用的属性:

     <PropertyGroup>
         <PetOpenApiSpecLocation>petshop-openapi-spec.json</PetOpenApiSpecLocation>
         <PetClientClassName>PetShopRestClient</PetClientClassName>
         <PetClientNamespace>PetShopRestClient</PetClientNamespace>
         <PetClientOutputDirectory>PetShopRestClient</PetClientOutputDirectory>
     </PropertyGroup>
    

    添加以下目标:

     <Target Name="generatePetClient" BeforeTargets="CoreCompile" Inputs="$(PetOpenApiSpecLocation)" Outputs="$(PetClientOutputDirectory)\$(PetClientClassName).cs">
         <Exec Command="$(NSwagExe) openapi2csclient /input:$(PetOpenApiSpecLocation)  /classname:$(PetClientClassName) /namespace:$(PetClientNamespace) /output:$(PetClientOutputDirectory)\$(PetClientClassName).cs" ConsoleToMSBuild="true">
         <Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
       </Exec>
     </Target>
     <Target Name="forceReGenerationOnRebuild" AfterTargets="CoreClean">
        <Delete Files="$(PetClientOutputDirectory)\$(PetClientClassName).cs"></Delete>
     </Target>
    

    请注意,此目标使用 <BeforeTarget 和 AfterTarget 的属性来定义生成顺序。 第一个名为 generatePetClient 的目标将在核心编译目标之前执行,因此将在编译器执行之前创建源。 输入参数和输出参数与增量生成相关。 MSBuild 可以将输入文件的时间戳与输出文件的时间戳进行比较,并确定是跳过、生成还是部分重新生成目标。

    在项目中安装 NSwag.MSBuild NuGet 包后,可以使用 .csproj 文件中的变量 $(NSwagExe) 在 MSBuild 目标中运行 NSwag 命令行工具。 这样,即可通过 NuGet 轻松更新工具。 在这里,你将使用 Exec MSBuild 任务,通过所需的参数执行 NSwag 程序来生成客户端 REST API。 请参阅 NSwag 命令和参数

    可以通过将 ConsoleToMsBuild="true" 添加到 <Exec> 标记并使用 <Output> 标记中的 ConsoleOutput 参数捕获输出,从 <Exec> 中捕获输出。 ConsoleOutputItem的形式返回输出。 将剪裁掉空格。 当 ConsoleToMSBuild 为 true 时,ConsoleOutput 被启用。

    调用 forceReGenerationOnRebuild 的第二个目标在清理期间删除生成的类,以便在重建目标执行时强制重新生成代码。 此目标在 CoreClean MSBuild 预定义目标之后运行。

  8. 执行 Visual Studio 解决方案重新生成,并查看在 PetShopRestClient 文件夹上生成的客户端。

  9. 现在,使用生成的客户端。 转到客户端 Program.cs,并复制以下代码:

    using System;
    using System.Net.Http;
    
    namespace PetReaderExecTaskExample
    {
       internal class Program
       {
           private const string baseUrl = "https://petstore.swagger.io/v2";
           static void Main(string[] args)
           {
               HttpClient httpClient = new HttpClient();
               httpClient.BaseAddress = new Uri(baseUrl);
               var petClient = new PetShopRestClient.PetShopRestClient(httpClient);
               var pet = petClient.GetPetByIdAsync(1).Result;
               Console.WriteLine($"Id: {pet.Id} Name: {pet.Name} Status: {pet.Status} CategoryName: {pet.Category.Name}");
           }
       }
    }
    

    注意

    此代码使用 new HttpClient(),因为它易于演示,但它不是实际代码的最佳做法。 最佳做法是使用 HttpClientFactory 创建一个 HttpClient 对象,用于解决资源耗尽或过时 DNS 问题等 HttpClient 请求的已知问题。 请参阅使用 IHttpClientFactory 实现复原 HTTP 请求

祝贺! 现在,可以执行程序以查看其工作原理。

选项 2:派生自 ToolTask 的自定义任务

在许多情况下,使用 Exec 任务足以执行外部工具以执行 REST API 客户端代码生成等操作,但如果想要允许 REST API 客户端代码生成,并且仅当不使用绝对 Windows 路径作为输入时,该怎么办? 或者,如果需要以某种方式计算可执行文件所在的位置,该怎么办? 在需要执行某些代码来执行额外工作的情况下,MSBuild 工具任务 是最佳解决方案。 ToolTask 类是从 MSBuild Task派生的抽象类。 可以定义可创建自定义 MSBuild 任务的具体子类。 使用此方法可以运行为命令执行做准备所需的任何代码。 应先阅读教程“为代码生成创建自定义任务”

你将创建派生自 MSBuild ToolTask 的自定义任务,这将生成 REST API 客户端,但如果尝试使用 http 地址引用 OpenAPI 规范,则会发出错误。 NSwag 支持作为 OpenAPI 规范输入的 http 地址,但出于此示例的目的,假设有一个设计要求禁止该地址。

完整代码位于此 PetReaderToolTaskExample 文件夹中;你可以下载并查看。 在本教程中,你将逐步学习一些概念,这些概念可以应用于自己的方案。

  1. 为自定义任务创建新的 Visual Studio 项目。 将其称为 RestApiClientGenerator 并结合使用类库 (C#) 模板和 .NET Standard 2.0。 将解决方案命名为 PetReaderToolTaskExample

  2. 删除 Class1.cs,该文件是自动生成的。

  3. 添加 Microsoft.Build.Utilities.Core NuGet 包:

    • 创建名为 RestApiClientGenerator 的类

    • 继承自 MSBuild ToolTask 并实现抽象方法,如以下代码所示:

      using Microsoft.Build.Utilities;
      
      namespace RestApiClientGenerator
      {
          public class RestApiClientGenerator : ToolTask
          {
              protected override string ToolName => throw new System.NotImplementedException();
      
              protected override string GenerateFullPathToTool()
              {
                  throw new System.NotImplementedException();
              }
          }
      }
      
  4. 添加以下参数:

    • InputOpenApiSpec,规范所在位置
    • ClientClassName,生成的类的名称
    • ClientNamespaceName,生成类的命名空间
    • FolderClientClass,指向类所在的文件夹的路径
    • NSwagCommandFullPath,NSwag.exe 所在目录的完整路径
         [Required]
         public string InputOpenApiSpec { get; set; }
         [Required]
         public string ClientClassName { get; set; }
         [Required]
         public string ClientNamespaceName { get; set; }
         [Required]
         public string FolderClientClass { get; set; }
         [Required]
         public string NSwagCommandFullPath { get; set; }
    
  5. 安装 NSwag 命令行工具。 你需要 NSwag.exe 所在目录的完整路径。

  6. 实现抽象方法:

       protected override string ToolName => "RestApiClientGenerator";
    
       protected override string GenerateFullPathToTool()
       {
           return $"{NSwagCommandFullPath}\\NSwag.exe";
       }
    
  7. 可以重写的方法有很多。 对于当前实现,请定义以下两个:

    • 定义命令参数:
      protected override string GenerateCommandLineCommands()
      {
          return $"openapi2csclient /input:{InputOpenApiSpec}  /classname:{ClientClassName} /namespace:{ClientNamespaceName} /output:{FolderClientClass}\\{ClientClassName}.cs";
      }
    
    • 参数验证:
    protected override bool ValidateParameters()
    {
          //http address is not allowed
          var valid = true;
          if (InputOpenApiSpec.StartsWith("http:") || InputOpenApiSpec.StartsWith("https:"))
          {
              valid = false;
              Log.LogError("URL is not allowed");
          }
    
          return valid;
    }
    

    备注

    可以在 MSBuild 文件上以其他方式完成这种简单的验证,但建议在 C# 代码中执行此操作,并封装命令和逻辑。

  8. 生成项目。

创建控制台应用以使用新的 MSBuild 任务

下一步是创建使用该任务的应用。

  1. 创建 控制台应用 项目,并将其命名为 PetReaderToolTaskConsoleApp。 选择 .NET 6.0。 将其标记为启动项目。

  2. 创建 类库 项目以生成名为 PetRestApiClient的代码。 使用 .NET Standard 2.1。

  3. PetReaderToolTaskConsoleApp 项目中,创建一个到 PetRestApiClient的项目依赖。

  4. PetRestApiClient 项目中,创建文件夹 PetRestApiClient。 此文件夹将包含生成的代码。

  5. 请删除自动生成的 Class1.cs

  6. PetRestApiClient上,添加以下 NuGet 包:

    • Newtonsoft.Json,编译生成的客户端时需要
    • System.ComponentModel.Annotations,编译生成的客户端时需要
  7. PetRestApiClient 项目中,创建名为 petshop-openapi-spec.json 的文本文件(在项目文件夹中)。 若要添加 OpenAPI 规范,请将此处 的内容复制到文件中。 我们喜欢仅依赖于输入的可重现生成,如前所述。 在此示例中,如果用户选择 URL 作为 OpenAPI 规范输入,将引发生成错误。

    重要

    常规重新生成不起作用。 你会看到一条错误,指出无法复制或删除“RestApiClientGenerator.dll”。 这是因为正在尝试在使用它的同一个生成过程中生成 MBuild 自定义任务。 选择 PetReaderToolTaskConsoleApp 并仅重新生成该项目。 另一个解决方案是将自定义任务放在完全独立的 Visual Studio 解决方案中,就像 教程:创建自定义任务 示例一样。

  8. 将以下代码复制到 Program.cs

     using System;
     using System.Net.Http;
     namespace PetReaderToolTaskConsoleApp
     {
       internal class Program
       {
           private const string baseUrl = "https://petstore.swagger.io/v2";
           static void Main(string[] args)
           {
               HttpClient httpClient = new HttpClient();
               httpClient.BaseAddress = new Uri(baseUrl);
               var petClient = new PetRestApiClient.PetRestApiClient(httpClient);
               var pet = petClient.GetPetByIdAsync(1).Result;
               Console.WriteLine($"Id: {pet.Id} Name: {pet.Name} Status: {pet.Status} CategoryName: {pet.Category.Name}");
           }
       }
     }
    
  9. 更改 MSBuild 说明以调用任务并生成代码。 按照以下步骤编辑 PetRestApiClient.csproj

    1. 注册 MSBuild 自定义任务的使用:

      <UsingTask TaskName="RestApiClientGenerator.RestApiClientGenerator" AssemblyFile="..\RestApiClientGenerator\bin\Debug\netstandard2.0\RestApiClientGenerator.dll" />
      
    2. 添加执行任务所需的一些属性:

       <PropertyGroup>
          <!--The place where the OpenAPI spec is in-->
         <PetClientInputOpenApiSpec>petshop-openapi-spec.json</PetClientInputOpenApiSpec>
         <PetClientClientClassName>PetRestApiClient</PetClientClientClassName>
         <PetClientClientNamespaceName>PetRestApiClient</PetClientClientNamespaceName>
         <PetClientFolderClientClass>PetRestApiClient</PetClientFolderClientClass>
         <!--The directory where NSawg.exe is in-->
         <NSwagCommandFullPath>C:\Nsawg\Win</NSwagCommandFullPath>
        </PropertyGroup>
      

      重要

      根据系统上的安装位置选择适当的 NSwagCommandFullPath 值。

    3. 在生成过程中添加 MSBuild 目标 以生成客户端。 在执行 CoreCompile 生成编译中使用的代码之前,应执行此目标。

      <Target Name="generatePetClient" BeforeTargets="CoreCompile" Inputs="$(PetClientInputOpenApiSpec)" Outputs="$(PetClientFolderClientClass)\$(PetClientClientClassName).cs">
        <!--Calling our custom task derivated from MSBuild Tool Task-->
        <RestApiClientGenerator InputOpenApiSpec="$(PetClientInputOpenApiSpec)" ClientClassName="$(PetClientClientClassName)" ClientNamespaceName="$(PetClientClientNamespaceName)" FolderClientClass="$(PetClientFolderClientClass)" NSwagCommandFullPath="$(NSwagCommandFullPath)"></RestApiClientGenerator>
      </Target>
      
      <Target Name="forceReGenerationOnRebuild" AfterTargets="CoreClean">
        <Delete Files="$(PetClientFolderClientClass)\$(PetClientClientClassName).cs"></Delete>
      </Target>
      

    InputOutput增量生成相关,forceReGenerationOnRebuild 目标会在 CoreClean后删除生成的文件,这会强制在重新生成操作期间重新生成客户端。

  10. 选择 PetReaderToolTaskConsoleApp 并仅重新生成该项目。 客户端代码已经生成并完成编译。 可以执行它并查看其工作原理。 该代码从文件生成代码,这是被允许的。

  11. 在此步骤中,你将演示参数验证。 在 PetRestApiClient.csproj中,更改属性 $(PetClientInputOpenApiSpec) 以使用 URL:

      <PetClientInputOpenApiSpec>https://petstore.swagger.io/v2/swagger.json</PetClientInputOpenApiSpec>
    
  12. 选择 PetReaderToolTaskConsoleApp 并仅重新生成该项目。 根据设计要求,你将收到错误“不允许使用 URL”。

下载代码

安装NSwag 命令行工具。 然后,你需要获取 NSwag.exe 所在目录的完整路径。 之后,编辑 PetRestApiClient.csproj,并根据计算机上的安装路径选择正确的 $(NSwagCommandFullPath) 值。 现在,选择 RestApiClientGenerator 并仅生成该项目,最后选择并重新生成 PetReaderToolTaskConsoleApp。 可以执行 PetReaderToolTaskConsoleApp。 验证一切是否按预期工作。

后续步骤

你可能想要将自定义任务发布为 NuGet 包。

或者,了解如何测试自定义任务。