9.创建更好的 Web API

Web API 是互联网的精髓。它们为开发人员提供了网络的开放性和访问互联网上任何数据的能力。但是,有一些特定于 API 的最佳实践。选择正确的 HTTP 动词的能力、如何记录 API 和测试 API 只是我们将介绍的一些主题。

话虽如此,本章涵盖的技术非常广泛且密集。我们将尝试打包尽可能多的信息来帮助构建高质量的 API。我们还将提供相关链接以供进一步研究。

在本章中,我们将介绍以下主要主题:

  • 快速创建 API
  • 设计 API
  • 测试 Web API
  • 标准化 Web API 技术

在本章中,我们将学习如何设计、创建、测试和记录 API,以及如何通过 CI/CD 管道对我们的 API 执行完整的端到端测试。

我们将通过回顾一些编写 API 的更常见技术来结束本章,例如使用正确的 HTTP 动词和状态代码、如何避免大量依赖资源、如何在 API 中实现分页、对 API 进行版本控制、使用 DTO 而不是实体,以及从 .NET 调用其他 API 的最佳方式。

技术要求

在 .NET 8 中,Web API 占据了主导地位。Visual Studio 添加了新功能,使 Web API 更易于构建和测试。对于本章,我们建议使用 Visual Studio 2022,但查看 GitHub 存储库的唯一要求是一个简单的文本编辑器。

第 09 章 的代码位于 Packt Publishing 的 GitHub 存储库中,可在 https://github.com/PacktPublishing/ASP.NET-Core-8-Best-Practices 找到。

快速创建 API

使用 .NET 8,API 集成到框架中,使创建、测试和记录变得更加容易。在本节中,我们将学习一种使用 Visual Studio 2022 创建最小 API 的快速简便方法,并逐步介绍它生成的代码。我们还将了解为什么最小 API 是构建基于 REST 的服务的最佳方法。

使用 Visual Studio

.NET 8 的功能之一是能够极快地创建最小 REST API。一种方法是使用 dotnet 命令行工具,另一种方法是使用 Visual Studio。为此,请按照以下步骤操作:

  1. 打开 Visual Studio 2022 并创建一个 ASP.NET Core Web API 项目。

  2. 选择项目的目录后,单击 下一步

  3. 在项目选项下,进行以下更改:

  • 取消选中 使用控制器 选项以使用最小 API
  • 选中 启用 OpenAPI 支持 以包含使用 Swagger 的 API 文档支持:

图 9.1 – Web 最小 API 项目的选项

在这里插入图片描述

  1. 单击 创建。就是这样 - 我们有一个简单的 API!它可能不多,但它仍然是一个带有 Swagger 文档的完整 API。Swagger 是一种用于创建 API 文档和实现 OpenAPI 规范的工具,而 Swashbuckle 是一个使用 Swagger 实现 Microsoft API 的 NuGet 包。如果我们查看该项目,会发现有一个名为 Program.cs 的文件。

  2. 打开 Program.cs 将显示整个应用程序。这是 .NET 的强项之一 - 能够相对快速地创建脚手架 REST API:

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild",
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
           {
               var forecast = Enumerable.Range(1, 5).Select(index
                                                            =>
                                                            new WeatherForecast
                                                            (
                                                                DateOnly.FromDateTime(DateTime.Now.AddDays
                                                                                      (index)),
                                                                Random.Shared.Next(-20, 55),
                                                                summaries[Random.Shared.Next(
                                                                    summaries.Length)]
                                                            ))
                   .ToArray();
               return forecast;
           })
    .WithName("GetWeatherForecast")
    .WithOpenApi();
app.Run();
internal record WeatherForecast(DateOnly Date,
                                int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 +
        (int)(TemperatureC / 0.5556);
}

在前面的代码中,我们通过 .CreateBuilder() 方法创建了“应用程序”。我们还添加了 EndpointsAPIExplorerSwaggerGen 服务。EndpointsAPIExplorer 使开发人员能够在 Visual Studio 中查看所有端点,我们将在后面介绍。另一方面,SwaggerGen 服务在通过浏览器访问时为 API 创建文档。下一行使用 **.**Build() 方法创建我们的应用程序实例。

  1. 一旦我们有了应用程序实例并且处于开发模式,我们就可以添加 Swagger 和 Swagger UI。.UseHttpsRedirection() 旨在当网页协议为 HTTP 时重定向到 HTTPS,以确保 API 安全。
  2. 下一行使用 .MapGet() 创建我们的 GET weatherforecast 路由。我们添加了 .WithName().WithOpenApi() 方法来分别识别要调用的主要方法并让 .NET 知道它使用 OpenAPI 标准。最后,我们调用了 app.Run()
  3. 如果我们运行该应用程序,我们将看到有关如何使用我们的 API 以及可用内容的记录 API。运行该应用程序会产生以下输出:

图 9.2 – 我们记录的 Web API 的屏幕截图

在这里插入图片描述

如果我们调用 /weatherforecast API,我们会看到我们收到带有 200 HTTP 状态的 JSON。

图 9.3 – 我们的 /weatherforecast API 的结果

在这里插入图片描述

将这个小 API 视为中间件,其中 API 控制器组合成一个紧凑文件 (Program.cs)。

为什么要使用最小 API?

我认为最小 API 是 .NET 8 中的一项功能,尽管它是一种语言概念。如果应用程序非常大,添加最小 API 应该是一个有吸引力的功能,有四个方面:

  • 自包含:一个文件中的简单 API 功能很容易被其他开发人员遵循
  • 性能:由于我们不使用控制器,因此使用这些 API 时不需要 MVC 开销
  • 跨平台:使用 .NET,API 现在可以部署在任何平台上
  • 自文档化:虽然我们可以将 Swashbuckle 添加到其他 API,但它也会为最小 API 构建文档

接下来,我们将采用这些最小 API 并开始研究 Visual Studio 的测试功能。

在本节中,我们在 Visual Studio 中创建并审查了一个最小 API 项目,以及为什么最小 API 对我们的项目很重要。

设计 API

在本节中,我们将介绍向用户提供直观清晰的 API 的最佳方法。API 的设计应该经过深思熟虑,并且在用户希望发出请求时有意义。

要创建真正基于 REST 的 API,我们必须使用不同的思维方式。我们必须将自己视为用户而不是开发人员。在编写 API 时,API 的用户其他开发人员。

断开与现有模式的连接

在设计 API 时,我们需要从用户的角度出发,而不是基于类层次结构或数据库模式来设计 API。虽然开发人员可能会考虑基于类层次结构或数据库模式创建 API 作为捷径,但它可能会增加检索数据时使用哪种资源的复杂性。一个例子是使用订单资源来查找联系人。虽然 Entity Framework Core 中的 Order 实体可能包含 Company 属性,并且我们需要公司的联系方式,但我们不会写 https://www.myfakesite.com/Order/15/Company/Contact。应避免基于现有层次结构或架构来构建 URL 结构。

在设计合理的 API 时,忽略现有架构至关重要。用新的眼光看待 API 以获得最佳设计。最流行的 API 是最干净、最直观的,因为它们使用 collection/item/collection 语法。一个很好的例子是 /orders/15/companys

识别资源

在系统中,查看用户如何与网站交互并从特定场景中提取名词。这些将成为 API 的资源。

例如,用户可以在购物车系统中执行以下操作:

  • 查看产品列表
  • 查看产品
  • 将产品添加到购物车
  • 从购物车中删除产品
  • 结帐

从这些场景中,我们可以提取以下资源:

  • 产品
  • 产品
  • 购物车

我们开始根据整个系统使用的资源识别和逻辑划分我们的 API。

从这里,我们可以根据每个场景将 HTTP 动词应用于每个资源。

将 HTTP 动词与资源关联起来

一旦我们拥有了主要资源,我们就可以根据上一节中定义的特定场景将 HTTP 动词应用于每个资源。

创建 API 时,可能很容易使用名词/动词语法 - 例如 https://www.myurl.com/products/get 或 https://www.myurl.com/getproducts。这种方法适得其反,因为已经存在用于此目的的 Web 标准。

虽然这确实有效,但它违反了一些 REST 原则(我们将在以下部分中讨论标准化 Web API 技术时介绍这些原则)。现在,让我们一步一步地创建一个简单的购物车 API。

每个 HTTP 动词都有一个基于其上下文的默认操作:

  • GET:返回资源
  • POST:创建新资源
  • PUT:根据标识符替换整个资源
  • PATCH:根据标识符更新资源中的特定项目
  • DELETE:删除资源

例如,上一节中的场景可以根据资源及其动词开始成形:

  • GET /api/products:查看产品列表
  • GET /api/product/{id}:查看产品
  • POST /api/cart/{cartId}:使用 POST 数据将产品添加到购物车(即 new { ProductId = {productId}, Qty = 1 }
  • PATCH /api/cart/{cartId}:使用 POST 数据从购物车中删除产品(即 new { ProductId = {productId} })
  • GET /api/cart/{cartId}:检索购物车中包含所有产品的购物车
  • POST /api/cart/{cartId}/checkout:结账

一旦我们将资源与已定义的场景匹配,我们就可以继续向调用者返回状态代码。

返回 HTTP 状态代码

定义资源后,我们需要知道请求是否成功。这就是我们返回 HTTP 状态代码的地方。

这些状态代码分为以下类别:

  • 1xx:信息代码
  • 2xx:成功代码
  • 3xx:重定向代码
  • 4xx:客户端代码
  • 5xx:服务器错误

与单元测试类似,我们查看“正常”路径和损坏路径。但对于 API,我们需要添加不可恢复的路径,以防发生不可恢复的错误。

让我们看看两个 URL 以及它们应该返回的状态代码。

GET /api/products 将返回以下状态代码:

  • 200 成功:产品已成功返回
  • 500 内部服务器错误:如果出现问题,则可选

如果 API 成功,它将返回带有 200 状态代码的产品列表。如果出现问题,它将返回 500 状态代码。API 还可以返回其他状态代码。例如,如果针对特定用户进行 API 调用,API 可能会返回 401,即未授权状态代码。

POST /api/cart/{cartId} 带有帖子正文 (new { ProductId = {productId}, Qty = 1 }) 将返回以下状态代码:

  • 201 Created:商品已创建并添加到购物车
  • 202 Accepted:商品已添加到购物车
  • 404 Not Found:未找到购物车或产品
  • 500 Internal Server Error:发生不可恢复的错误

使用此 API,我们可以返回 201 Created 或 202 Accepted 状态代码。如果我们找不到要添加到购物车的购物车或产品,则返回 404 状态代码。否则,返回 500 状态代码。

虽然这两个示例并非一成不变,但它们应该为团队提供一个模板,以讨论哪些业务规则决定返回给用户的状态代码。无论返回什么状态代码,它们都应该提供足够的上下文,说明通过 API 发出的请求。

一些常见的 API 使用全有或全无的方法;它们要么返回 200,要么返回 500。这取决于我们想要向客户端发送多少信息。这些类型的 API 感觉缺少更多功能,例如未授权 (401) 或未找到 (404) 状态代码。最佳做法是向 API 的调用者提供尽可能多的信息。

HTTP 状态代码

HTTP 状态代码是 Web 开发中的标准,通过位于 https://www.rfc-editor.org/rfc/rfc9110#status.codes 的 RFC 征求意见 (RFC) 9110 提案提供。幸运的是,我们不需要记住所有代码,因为 .NET 在 https://learn.microsoft.com/en-us/dotnet/api/system.net.httpstatuscode 提供了一个包含每个状态代码的 HttpStatusCodeEnum 类,以及 IActionResults,例如 Ok(object)。可以在 https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.results.statuscode 找到特定状态代码。

在本节中,我们学习了如何设计 API 并分解了每个步骤 - 即断开与技术的联系、识别资源、了解应用于资源的正确动词以及为我们的 API 提供正确的响应代码。

在下一节中,我们将介绍两种测试 API 的方法:一种是在 Visual Studio 中使用新的 Endpoints Explorer,另一种是通过创建完整的集成测试。

测试 Web API

设计和创建 API 后,我们需要一种方法在 IDE 和集成测试中测试它们。幸运的是,Visual Studio 添加了新的 Endpoints Explorer。

在本节中,我们将学习两种测试 API 的方法。一种方法是通过使用 Visual Studio 的开发环境。第二种测试 API 的方法是通过集成测试。如果我们有 CI/CD 管道,这些管道将自动运行以确认我们的 API 是否按预期工作。

Visual Studio Endpoints Explorer

过去,使用 Visual Studio 的开发人员必须运行单独的工具来测试他们的 API,但随着最新版本的 .NET 8,Visual Studio 团队添加了一个名为 Endpoints Explorer 的新面板:

图 9.4 – 端点资源管理器

在这里插入图片描述

如果我们在 Program.cs 文件中定义了一组 API,则我们的集合将显示如下:

图 9.5 – Endpoints Explorer 中的 API 集合

在这里插入图片描述

右键单击 API 将在新的 HTTP 编辑器中生成请求。 HTTP 编辑器允许为列出的 API 自定义变量:

图 9.6 – HTTP 编辑器中的示例 API 集合

在这里插入图片描述

在 图 9.6 中,HTTP 编辑器使用以下命令发出 HTTP 请求:

  • @:为文件创建变量(例如,@variable = value
  • //:指定注释
  • ###:指定 HTTP 请求的结束
  • :创建基于 REST 的请求,包括 DELETEGETHEADOPTIONSPATCHPOSTPUTTRACE 请求
  • :在定义 URL 后直接添加标头,以便将其包含在请求中

定义 API 后,左侧边栏中会出现绿色箭头。运行应用程序以在本地测试 API。 在 API 运行时按下最左侧边缘的箭头将在右侧窗格中产生结果:

图 9.7 – /attractions 请求的结果

在这里插入图片描述

在此示例中,我们测试了 /attractions 请求,接收了数据并将其显示在右侧。

为什么这很重要?

通过使用此新的 Visual Studio 功能,我们获得了以下优势:

  • 集中式 API:我们在一个地方拥有所有 API 的目录
  • 自文档化:新加入项目的开发人员可以找到此 .http 文件,执行示例请求并了解每个 API 在系统中的作用
  • IDE 集成:测试我们的 API 不需要其他工具

此新功能对于希望在本地测试现有 API 的开发人员非常有帮助,并且还可以补充引入系统的新最小 API。

其他端点资源管理器材料

有关端点资源管理器的其他材料,Sayed Ibrahim Hashimi 在 https://devblogs.microsoft.com/visualstudio/web-api-development-in-visual-studio-2022/#endpoints-explorer 上提供了有关它可以做的所有事情的精彩文章。

在本节中,我们了解了 Endpoints Explorer、如何使用它来帮助本地测试 API 以及它的重要性。在下一节中,我们将使用我们的 API 并学习如何使用集成测试来快速产生结果。

集成测试 API

在上一节中,我们了解了如何使用 Endpoints Explorer 测试我们的 API。但是,我们不必在服务器上安装 Visual Studio 来测试我们的 API。

在本节中,我们将研究如何为我们的 API 应用集成服务器以实现完整的端到端测试。

当我们在 [第 8 章] 中创建单元测试时,我们创建了一个数据库的内存表示。我们可以创建一个类似的环境,在其中可以启动和拆除整个环境以进行 API 测试。

在我们的 CI/CD 管道中,我们可以为集成测试构建一个一次性服务器,以提供包含 API 和服务以及一次性数据库的完整端到端测试。

构建集成服务器

由于 .NET 为我们的应用程序提供了一个简单的 Program.cs 文件,我们可以包装整个应用程序,并用 Web 和数据库服务器替换我们想要模拟的服务。

我们可以使用 WebApplicationFactory 类设置环境。我们将最小 API 项目作为依赖项包含在我们的 Api.Tests 项目中。一旦我们在程序中有了依赖项,我们就可以创建我们的 WebApplicationFactory 类:

using System.Data.Common;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using ThemePark.Data.DataContext;
namespace ThemePark.Api.Tests;
public class TestThemeParkApiApplication :
WebApplicationFactory<Program>
{
    protected override IHost CreateHost(
        IHostBuilder builder)
    {
        var root = new InMemoryDatabaseRoot();
        builder.ConfigureServices(services =>
                                  {
                                      services.RemoveAll(typeof(
                                          DbContextOptionsBuilder<ThemeParkDbContext>
                                      ));
                                      services.AddScoped(sp => new
                                                         DbContextOptionsBuilder<ThemeParkDbContext>()
                                                         .UseInMemoryDatabase("TestApi", root)
                                                         .UseApplicationServiceProvider(sp)
                                                         .Options);
                                      services.AddDbContext<ThemeParkDbContext>(
                                          (container, options) =>
                                          {
                                              var connection = container
                                                  .GetRequiredService<DbConnection>();
                                              options.UseSqlite(connection);
                                          });
                                      services.AddTransient<IThemeParkDbContext,
                                      ThemeParkDbContext>();
                                  });
        return base.CreateHost(builder);
    }
}

在前面的代码示例中,我们从 WebApplicationFactory 继承。通用的 来自我们引用的包含依赖项。然后,我们为内存数据库创建了一个根,并通过删除 DbContextOptionsBuilder 的所有实例来配置我们的服务。删除这些实例后,我们可以使用更新后的数据库设置创建对同一类型的新范围引用。

接下来,我们使用 SQLite 数据库添加了新的 ThemeParkDbContext 和更新后的连接。请记住,Entity Framework Core 将使用 .EnsureCreated() 方法自动创建整个数据库的结构。最后,我们为应用程序中的服务添加了 IThemeParkDbContext 的依赖注入注册。

这就是我们的集成服务器。现在,我们可以在集成测试中使用 TestThemeParkApiApplication。例如,如果我们想为我们的 /attractions API 创建一个测试,我们的集成测试将如下所示:

using Microsoft.Extensions.DependencyInjection;
using ThemePark.Data.DataContext;
namespace ThemePark.Api.Tests;
[TestClass]
public class ApiTests
{
    private TestThemeParkApiApplication _app;
    [TestInitialize]
    public void Setup()
    {
        _app = new TestThemeParkApiApplication();
        using (var scoped = _app.Services.CreateScope())
        {
            var context = scoped.ServiceProvider
                .GetService<IThemeParkDbContext>();
            context?.Database.EnsureCreated();
        }
    }
    [TestMethod]
    [TestCategory("Integration")]
    public async Task GetAllAttractions()
    {
        // Arrange
        var client = _app.CreateClient();
        var expected = TestData.ExpectedAttractionData;
        // Act
        var response = await
            client.GetAsync("/attractions");
        var actual = await response.Content
            .ReadAsStringAsync();
        // Assert
        Assert.AreEqual(expected, actual);
    }
}

在前面的代码片段中,我们在设置时初始化了 TestThemeParkApiApplication,以便每个实例都是通过 .EnsureCreated() 方法新建的。_app.CreateClient 为我们提供了 HttpClient 来调用 URL。我们调用我们的 /attractions API 并将其与我们创建的资源字符串进行比较,而不是用大型 JSON 字符串弄乱我们的测试方法。最后,我们的测试将 JSON 结果与 API 返回的结果进行比较。

能够创建整个前到后集成测试,证明 API、Entity Framework 查询和数据库代码在通过 CI/CD 管道运行时按预期工作,并且测试成功,这应该会让我们对代码充满信心。

在本节中,我们学习了如何获取 API 并在 Visual Studio 的 Endpoints Explorer 中测试它们。我们还学习了如何通过使用 WebApplicationFactory 包装我们的 API 项目来获取这些 API 并使它们在 CI/CD 管道中可测试。

在下一节中,我们将介绍行业中构建 API 时使用的一些常见做法。

标准化 Web API 技术

在本节中,我们将学习如何正确使用 HTTP 动词和状态代码、如何避免大量依赖资源、如何为 API 创建分页、如何对 API 进行版本控制、使用 DTO 而不是实体,以及从 .NET 进行 API 调用的最佳方式。

使用正确的 HTTP 动词和状态代码

到目前为止,我们已经了解了如何使用 HTTP 动词以及如何返回状态代码。虽然这看起来似乎是一件微不足道的事情,但有些系统会忽略这些标准,并且始终使用 POST,而不管功能如何。

Swagger 提供了一个用于记录 API 的出色模板,借助 Visual Studio 的新 Endpoints Explorer,Visual Studio 将此基本文档带到开发人员的 IDE,使 API 更易于阅读和在其他项目中实施,向开发人员展示要使用哪些动词以及预期的状态代码。

在本章前面的购物车 API 示例中,用户将产品添加到购物车并继续结账。他们将使用购物车开始此过程。结账功能使我们使用购物车 API 和结账方法 (/cart/checkout),这非常合理。我们应该采取用户的操作并将其与 API 中的操作相匹配。

注意依赖资源

但是我应该根据资源将 API 发挥到什么程度?如果一个资源属于另一个资源,而另一个资源又依赖于另一个资源,等等,该怎么办?

以下是一个示例:/users/{userId}/projects/{projectId}/tasks

我们想要获取用户在某个项目上的任务,但这个 URL 似乎有点长,不是吗?我们如何将其分解为更易于管理的内容?任何超过三级深度的内容都是自找麻烦。

此 URL 需要更细粒度的方法 - 即分解每个资源。与前面的 URL 相比,更好的方法是使用 /users/{userId}/projects 来检索用户正在处理的项目列表。下一个 URL 将根据所选项目提供任务,看起来像 /projects/{projectId}/tasks

作为开发人员,我们都知道一切都是妥协。在这种情况下,我们提供了一个更简单的 API,但需要两次调用而不是一次。

这些是需要与团队成员讨论的问题,但本质上,URL 越短,实现起来就越容易。URL 越长,满足请求所需的资源查找就越多。

API 结果中的分页

对于大多数 API 调用,结果以原始的 JavaScript 对象表示法 (JSON) 格式返回,通常作为集合或单个项目。如果客户端需要分页结果,而他们现在只想要一页数据,该怎么办?

为了帮助客户端开发人员,JSON 结果可以包含以下内容:

{
    "total": 7,
    "pageSize": 7,
    "currentPage": 1,
    "next": false,
    "previous": false,
    "results": [
        {
            "id": 1,
            "name": "Twirly Ride",
            "locationId": 2,
            "locationName": "Fantasy"
        },
        {
            "id": 2,
            "name": "Mine car Coaster",
            "locationId": 5,

虽然通常需要将结果作为集合返回,但标头中要返回的一些字段如下:

  • Total:记录总数
  • PageSize:此响应中返回了多少条记录
  • TotalPages:根据 PageSize 指定总页数
  • CurrentPage:指定当前所在的页面
  • NextPrevious:是否有足够的记录可以前后移动?
  • Sort:指定结果的排序方式
  • Filter:指定对结果应用了什么过滤器

标头旨在帮助我们的客户端开发人员充分利用响应。虽然这不是要包含的字段的完整列表,但在客户端上显示记录子集时,应在每个响应中一致地实现它。

应避免在标头中使用“状态代码”字段或“成功”字段,因为 HTTP 状态代码被视为预期响应。

版本控制 API

默认情况下,创建 API 时,它们很可能处于原始状态,没有版本控制。版本控制有四种类型:

  • 无版本控制:当我们创建第一个 API 时

  • URI 版本控制:将另一个段放入 URL 中非常常见且值得推荐 (/v1/users)

  • 查询字符串:这涉及将查询字符串附加到 URL 末尾 (/users/?version=1)

  • 标头版本控制:这涉及使用自定义标头将版本放入标头:

GET /users
Custom-Header: api-version=1

最常用的版本控制技术是 URI 版本控制。虽然每个人的理解可能有所不同,但这种技术很有吸引力,因为我们可以立即知道我们正在使用哪个版本。

使用 DTO,而不是实体!

在测试我们的 API 时,我们没有返回实体(景点位置)。相反,我们返回的是数据传输对象DTO),它是属性的一个子集。

我们的安全章节([第 4 章])提到,在涉及主键或敏感信息时不要暴露太多。DTO 让开发人员有机会选择应该向客户端公开哪些属性。

例如,我们的 Attraction DTO 旨在提供最少量的信息;我们将在查看以下代码示例后讨论这个问题:

public static class AttractionExtensions
{
    public static AttractionDto ToDto(
    this Attraction attraction)
    {
        return new AttractionDto
        {
            Id = attraction.Id,
            Name = attraction.Name,
            LocationId = attraction.LocationId,
            LocationName = attraction.Location == null
                ? string.Empty
                : attraction.Location.Name
        };
    }
}

这里,我们有一个简化的 AttractionDto 类,其中包含简单属性。我们还有一个基于依赖的 Location 类的 LocationName 属性。

虽然我们将其作为 .ToDto() 方法,但我们可以创建其他 DTO 扩展方法,以在 .ToDifferentDto() 方法中返回不同的数据,或者我们想叫它什么。

使用 DTO 而不是 Entity Framework 实体的另一个原因是导航属性的潜在递归性质。当从 API 返回实体时,它会变成 JSON 对象。如果我们有一个嵌套实体,它将沿着链向下跟踪它。最好将实体的属性隔离并提炼为其本机类型,以便在从 API 返回时在客户端进行基本使用。

避免使用 HttpClient 的新实例

虽然本章的大部分内容讨论了创建和测试 API,但我觉得我们也需要提到如何在 .NET 应用程序中使用它们。

有多种方式可以使用 Web API,例如使用 WebRequestWebClient,但对于大多数目的,建议使用 HttpClient 类,因为它具有灵活性和现代化。WebRequestWebClient 类用于旧版应用程序的转换。话虽如此,创建一个新的 HttpClient 实例很容易,但这不是最好的方法。

Microsoft 表示,HttpClient 应该在应用程序的整个生命周期内仅使用一次。如果我们在应用程序的多个位置创建 HttpClient 实例,则会阻碍性能和可扩展性机会。如果请求率过高,会导致称为 TCP 端口耗尽的问题,因此最好避免使用以下代码:

// Bad use of HttpClient
var client = new HttpClient();

一些开发人员可能会更进一步,通过包装 using 语句来正确处理 HttpClient 类,认为以下代码片段更好:

// Still not good
using (var client = new HttpClient())
{
    .
    .
}

这段代码的问题在于,我们仍在创建 HttpClient 的另一个实例,仍然会导致端口耗尽,并且仍然会在我们以后很可能需要它时将其丢弃。

在 .NET Core 2.1 中,Microsoft 创建了一个 IHttpClientFactory 类来提供 HttpClient 的单个实例。我们可以简单地向它请求一个 HttpClient 实例,然后我们就会收到一个。最好的消息是它可以进行依赖注入。

一旦我们通过构造函数注入了该类,代码就变得更容易使用,如以下代码片段所示:

public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;
    private readonly IHttpClientFactory _factory;
    public IndexModel(
        ILogger<IndexModel> logger,
        IHttpClientFactory factory)
    {
        _logger = logger;
        _factory = factory;
    }
    public async Task OnGet()
    {
        // Bad use of HttpClient
        // var client = new HttpClient();
        // Still not good
        //using (var client = new HttpClient())
        //{
        //    .
        //    .
        //}
        // Best way to use HttpClient
        var client = _factory.CreateClient();
        // use client.GetAsync("https://www.google.com") to
           grab HTML
    }
}

当我们使用 .CreateClient()HttpClientFactory 请求客户端时,除非必须,否则它不会创建 HttpClient 的新实例。

可以将 .CreateClient() 方法视为在后台使用单例设计模式,类似于此处显示的代码:

private static HttpClient _client { get; set; }
public HttpClient CreateClient()
{
    if (_client == null)
    {
        _client = new HttpClient();
    }
    return _client;
}

顺便提一下,上述代码不是线程安全的;它是为了展示单例设计模式的概念而提供的。

我们总是会得到一个 HttpClient 实例,这是进行服务器端 API 调用的更好方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0neKing2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值