使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

内容分享2小时前发布
0 0 0

本章涵盖

  • 查看系统要求
  • 选择和安装集成开发环境 (IDE)
  • 创建新的 ASP.NET Web API 项目
  • 配置项目的启动和设置文件
  • 调试、测试和改善我们的第一个项目

在本章中,我们将创建MyBGList Web API,这是我们在第1章中介绍的面向服务的体系结构具体方案的基石。更具体地说,我们将穿上负责创建和设置项目的软件开发团队的鞋子。我们必须做出一些高级决策,例如选择要采用的集成开发环境 (IDE),然后切换到更实用的方法,例如首次获取源代码,以及调试和测试它以确保它按预期工作。

在本章结束时,您将能够通过解决有关所涵盖主题和概念的一些总结练习来测试您的知识。主要目标是创建一个工作 ASP.NET Web API 项目,你将在以下章节中扩展和改善该项目。

2.1 系统要求

由于我们已经选择使用 ASP.NET Core 开发 Web API,因此回顾一下系统要求(我们需要安装什么才能开始开发之旅)可能会很有用。

2.1.1 .NET 开发工具包

要获取的最重大的工具是 .NET 软件开发工具包(更广为人知的 .NET SDK),其中包含 .NET 命令行界面 (.NET CLI)、.NET 库和三个可用的运行时:

  • ASP.NET 核心运行时,运行 ASP.NET 核心应用所必需的
  • 桌面运行时,运行 WPF 和 Windows 窗体应用所必需的
  • .NET 运行时,承载前两个运行时所需的一组低级库和接口

注意.NET SDK 可作为适用于 Windows、Linux 和 macOS 的独立包提供,网址如下:
https://dotnet.microsoft.com/download

2.1.2 集成开发环境

从严格的角度来看,安装.NET SDK是我们开始构建任何类型的.NET应用程序所需要做的就是,包括我们的 ASP.NET Core Web API。但是,使用 Windows、Linux 或 macOS 中可用的内置工具(例如默认文本编辑器和命令行终端)编写源代码远非理想。经验丰富的开发人员可以自豪地说他们可以通过使用记事本,Emacs或vim做任何事情的日子早已一去不复返了。实际上,现代 IDE 包含大量有用的功能,这些功能无疑会提高任何愿意学习如何高效利用它们的软件开发人员的生产力。我不只是在谈论语法突出显示、代码完成和其他“外观”功能。现代 IDE 之所以出色,是由于它们能够提供工具和扩展,使开发团队能够标准化和自动化某些流程,从而支持面向 DevOps 的方法:任务运行器、包管理器、代码检查器、集成源代码控制、自动部署系统、安全凭据存储、语法突出显示等等。

出于所有这些缘由,由于我们想设身处地为MyBGList软件开发团队着想,我们将使用IDE(或具有高级功能的代码编辑器)构建我们的Web API。Microsoft提供了两种选择:

  • Visual Studio – Visual Studio 是适用于 Windows 和 macOS 的综合 .NET 开发解决方案,包括编译器、代码完成工具、图形设计器和许多有用的功能,用于增强软件开发体验和生产力。
  • Visual Studio Code – Visual Studio Code 是一个适用于 Windows、Linux 和 macOS 的轻量级开源代码编辑器。与专注于.NET开发的Visual Studio不同,该产品采用与框架无关,与语言无关的方法。但它提供了一个丰富的扩展生态系统,以添加对大多数开发框架和编程语言的支持,包括 ASP.NET Core和C#。

注意许多非Microsoft替代品值得一提,例如JetBrains的Rider和Adobe的Brackets。但是,为了简单起见,我们将分析限制为Microsoft开发工具。

Visual Studio 和 Visual Studio Code 都可用于创建、配置和维护 ASP.NET Web API 项目。但是,由于我们希望在完成任务时充分探索.NET和 ASP.NET Core框架,因此我们将选择Visual Studio。具体来说,我们将使用 Visual Studio 2022,这是撰写本文时可用的最新版本。

注意如果你想使用Visual Studio Code或其他编辑器,不要担心。本书中的所有代码示例,以及 GitHub 存储库中提供的所有内容,也将在这些编辑器中工作。

2.2 安装 Visual Studio

Visual Studio 2022 有三个版本,每个版本都有一组特定的受支持功能:

  • 社区版具有创建、开发、调试、测试和发布各种 .NET 应用所需的所有功能。此版本是免费使用的,但它只能用于开源项目、学术研究和课堂学习,以及不超过五名开发人员的非企业组织。
  • 专业版具有社区版的所有功能,并且付费许可的限制较少。
  • 企业版具有其他两个版本的所有功能,以及一些高级架构级别的设计、验证、分析和测试工具。

注意就本书而言,您有权通过以下网址下载并安装社区版:
https://visualstudio.microsoft.com

Visual Studio 安装过程分为三个阶段:

  1. 下载并安装 Visual Studio Installer,它充当整个 Visual Studio 系列的安装和更新管理工具。
  2. 选择工作负载、单个组件和语言包。每个工作负载都包含我们要使用的框架、编程语言和平台所需的一组组件。在我们的场景中,由于我们要开发 ASP.NET 核心 Web API,因此我们只需要安装 ASP.NET 和 Web 开发工作负载(参见图 2.1),而无需安装其他单个组件 — 至少目前是这样。语言包取决于我们希望如何设置图形用户界面 (GUI) 使用的语言。
  3. 安装 Visual Studio 2022 以及选定的工作负载、组件和语言包。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图 2.1 将 ASP.NET 和 Web 开发工作负载添加到 Visual Studio 安装

注意如果您是第一次阅读本书,安装英语语言包可能是一个不错的选择,以便 IDE 命令始终与示例和屏幕截图匹配。

2.3 创建网页接口项目

当我们在系统上安装了 .NET SDK 和 Visual Studio 2022 时,我们可以开始创建我们的第一个 Web API 项目。执行以下步骤:

  1. 启动 Visual Studio 2022。
  2. 选择“创建新项目”选项。
  3. 使用搜索框查找 ASP.NET 核心 Web API 项目模板,如图 2.2 所示。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图 2.2 在 Visual Studio 中查找 ASP.NET 核心 Web API 项目模板

配置模板相当容易。在我们的场景中,我们给项目命名 MyBGList,并接受其他默认设置,如图 2.3 所示。请务必选择 .NET 6.0 框架,这是撰写本文时可用的最新版本。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图 2.3 配置 ASP.NET 核心 Web API 项目

警告如果要使用其他 .NET 版本,可以自由执行此操作。但请记住,本书中的某些源代码示例可能需要进行一些更改才能使用框架更新和/或重大更改。

单击“创建”按钮后,Visual Studio 将立即为新的 MyBGList 项目生成源代码,将其添加到具有一样名称的解决方案中,然后在 IDE 中打开它。一切就绪!

在继续之前,让我们快速检查一切是否正常。按 F5 键(或单击最上面的工具栏中的“运行”按钮)以在调试模式下启动项目。如果我们正确执行了所有操作,Visual Studio 应该自动启动我们的默认浏览器,指向 https:/ /localhost:<someRandomPort> 并显示图 2.4 中显示的页面。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图2.4 MyBGList项目首次运行

正如我们所看到的,我们用于创建项目的Visual Studio Web API模板提供了一个简洁的起始页,其中提到了Swagger,这是一个描述API结构的便捷工具。我将在本章后面介绍 Swagger。目前,我们已准备好更好地了解我们的项目。

在我们开始编码之前,可能值得讨论项目和解决方案以及它们在Visual Studio生态系统中扮演的角色。简而言之,我们可以说

  • 项目是一组源代码文件,一般与一些(必需的)配置文件和一堆(可选)内容文件(如图标、图像和数据文件)一起编译到可执行文件、库或网站中。MyBGList项目包括一小组由Visual Studio生成的.cs和.json文件,用于使用我们选择的配置设置创建我们的 ASP.NET Web API。
  • 解决方案是我们可以用来组织一个或多个相关项目的容器。当我们打开一个解决方案时,Visual Studio 会自动打开该解决方案包含的所有项目。在我们的方案中,该解决方案仅包含我们的 MyBGList Web API 项目并共享其名称。

在以下章节中,我们将向解决方案中添加其他项目。具体来说,我们将创建一些类库,我们希望将其与 Web API 项目分开,以便我们可以在其他地方使用它们。这种方法提高了代码的可重用性,同时让我们有机会在同一 IDE 窗口中对项目进行逻辑分组和访问。

2.4 MyBGList项目概述

目前我们已经了解了Visual Studio的基础知识,让我们花一些宝贵的时间来回顾我们全新的Web API项目的自动生成源代码。默认的 ASP.NET Core Web API 模板提供了一个最小但方便的样板,可用于理解典型项目的基本结构,这正是我们在开始编写与棋盘游戏相关的 Web API 之前需要做的。

让我们从项目的文件结构开始。在“解决方案资源管理器”窗口中,我们看到项目包含一些重大文件:

  • launchSettings.json(在 /Properties/ 文件夹中)- 包含用于在开发模式下启动项目的设置
  • appsettings.json – 包含所有环境的应用程序配置设置以及嵌套的应用程序设置。Development.json,仅包含特定于开发环境的设置
  • program.cs – 第 1 章中介绍的引导类
  • 天气预报控制器.cs – 显示如何提供一些虚拟天气预报数据的示例控制器
  • WeatherForecast.cs – 控制器使用强类型方法提供示例天气预报 JavaScript 对象表明法 (JSON) 数据的最小数据传输对象 (DTO) 类

在下一节中,我们将简要回顾所有这些文件,以了解它们的用途以及它们在样板文件中扮演的角色。在执行代码审查时,我们还将对一些默认行为进行一些小而重大的更新,以更好地满足我们的需求。最终,我们将用我们自己的棋盘游戏主题类替换与天气预报相关的样板文件。

2.4.1 查看launchSettings.json

要查看的第一个文件是 launchSettings.json,位于 /Properties/ 文件夹中。顾名思义,此文件包含一些与项目启动方式相关的配置设置。但是,请务必了解,此文件以及包含的所有设置将仅在本地开发计算机中使用。换句话说,当我们在生产服务器上发布项目时,它不会与我们的应用一起部署。正如我们通过打开它看到的那样,配置设置分为三个主要部分(或 JSON 键):

  • “$schema”- 指向描述文件所用架构的 URL
  • “iisSettings” – 包含 IIS Express Web 服务器的一些基本配置设置
  • “配置文件”- 分为两个子部分:
    • “IIS Express ”- 包含 IIS Express Web 服务器的其他设置
    • “MyBGList”—包含特定于 Kestrel Web 服务器的设置

如果你不知道IIS Express和Kestrel是什么,让我们快速回顾一下背景故事。ASP.NET Core 为两个可用于本地开发的 Web 服务器提供内置支持:

  • IIS Express—Internet Information Services (IIS) Web 服务器的轻量级版本,自 Windows XP 和 Visual Studio 2012 起可用
  • Kestrel – 一个开源、跨平台、事件驱动的异步 HTTP 服务器实现,随 ASP.NET Core 的第一个版本一起引入

注意在Visual Studio 2010及更早版本中,用于开发目的的默认Web服务器是 ASP.NET 开发服务器,一般称为Cassini。

我们可以通过单击“开始”按钮右侧的箭头处理程序来选择在 Visual Studio 中运行应用时要使用的 Web 服务器。此处理程序是 Visual Studio 顶级工具栏上带有绿色箭头的按钮,如图 2.5 所示。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图 2.5 Visual Studio 的“开始”按钮

应用名称的选项(在我们的方案中为 MyBGList)对应于 Kestrel。如我们所见,我们还可以选择要使用的Web浏览器,以及目前可以跳过的其他一些选项。

launchSettings.json 文件的 iisSettings 和配置文件部分包含 IIS Express 和 Kestrel 的配置设置。对于每个服务器,我们可以选择要使用的 HTTP 和 HTTPS 端口、启动 URL、启动应用程序之前要设置的环境变量等。

如图 2.5 所示,如果我们目前单击“开始”按钮(或选择“调试”>“开始调试”或按 F5),我们将看到 SwaggerUI 页面,该页面处理在两个浏览器的 launchUrl 选项中配置的 swagger 终结点。请注意,无论我们选择哪个 Web 服务器,都会显示图 2.4 中显示的 SwaggerUI 页面,由于浏览器已配置为使用该端点。唯一明显改变的是用于建立HTTPS连接的本地TCP端口,由于Visual Studio在创建项目时随机确定它们。让我们借此机会规范化这些端口。

注意我们将在后面的章节中广泛使用 SwaggerUI 页面及其内容,同时实现我们的示例 Web API。此外,第11章深入讨论了Swagger。

打开 launchSettings.json 文件,并更改其内容,如以下清单所示。更新的行和值以粗体显示。

清单 2.1 修改了 launchSettings.json 文件

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:40080",                         ❶
      "sslPort": 40443                                                    ❶
    }
  },
  "profiles": {
    "MyBGList": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "swagger",                                             ❷
      "applicationUrl": "https://localhost:40443;http://localhost:40080", ❸
      "environmentVariables": {                                           ❹
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "swagger",                                             ❺
      "environmentVariables": {                                           ❻
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

IIS Express 本地 URL 和 TCP 端口,用于 HTTP 和 HTTPS

Kestrel 本地 URL 和 TCP 端口,用于 HTTP 和 HTTPS

红隼起始页

红隼环境变量

IIS Express 起始页

IIS 快速环境变量

正如我们所看到的,我们已经为IIS Express和Kestrel设置了40080(HTTP)和40443(HTTPS)TCP端口。这个简单的调整确保了本书中引用的URL在我们的本地代码库中正常工作,无论我们要使用的Web服务器如何。

内置 launchSettings.json 文件中指定的其余设置暂时足够好,因此我们可以保留它们。但是,在关闭文件之前,让我们仔细看看“环境变量”部分,其中包含IIS Express和Kestrel的单个ASPNETCORE_ENVIRONMENT环境变量。

2.4.2 配置应用设置.json

让我们转到 appsettings.json 文件,该文件将应用程序配置设置存储在 JSON 键值对中。如果我们查看MyBGList Web API项目的自动生成代码,我们可以看到Visual Studio创建了该文件的两个实例:

  • appsettings.json
  • 应用设置。开发.json

在了解这些文件的工作原理之前,探索 ASP.NET Core 中的运行时环境的概念可能会很有用。

运行时环境

Web 应用程序开发一般至少涉及三个主要阶段:

  • 开发,软件开发人员在其中执行调试会话
  • 暂存,其中选定的一组用户(或测试人员)执行内部和/或外部测试
  • 生产,其中应用可供最终用户使用

根据 .NET 约定,这些阶段称为环境,可以使用应用执行上下文中的DOTNET_ENVIRONMENT和/或ASPNETCORE_ENVIRONMENT环境变量进行设置。每当我们启动应用程序时,我们都可以通过相应地设置该环境变量来选择要面向的运行时环境。

提示如果我们还记得 launchSettings.json 文件的 “environmentVariables” 部分,我们已经知道如何在本地开发机器中设置 ASPNETCORE_ENVIRONMENT 变量。我们将在第 12 章部署 Web API 时学习如何在生产服务器中执行此操作。

应用设置文件

目前我们知道了所有这些,我们可以很容易地理解这两个appsettings文件的用途:

  • appsettings.json 旨在存储将由所有运行时环境使用的配置设置,除非它们被特定环境特定的文件覆盖或补充。
  • 应用设置。Development.json就是其中之一。放置在那里的设置将仅由开发环境使用(并被任何其他环境忽略),覆盖和/或集成 appsettings.json “通用”文件中存在的设置。

警告特定于环境的文件将在通用版本之后读取,从而覆盖其中存在的任何键值对。换句话说,如果我们使用开发环境运行我们的应用程序,则应用程序设置中存在每个键值对。Development.json 将被添加到 appsettings.json 文件中存在的键值对中,如果它们已经设置,则替换它们。

如果我们查看这两个 appsettings 文件,我们会看到一堆与日志相关的设置(在日志记录 JSON 键中),我们目前可以忽略这些设置。我们将有机会在第7章讨论日志记录技术时与他们一起玩。我们目前能做的是添加一个新的键/值对,后来可以协助我们。打开 appsettings.json 文件,并将以下行(粗体)添加到现有 JSON 中:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "UseDeveloperExceptionPage": false
}

这个新的配置设置将为我们提供一些 appsettings 文件的练习,还允许我们在稍后将实施的实现技术之间切换。

2.4.3 玩程序.cs文件

让我们转到程序.cs文件,我们在第 1 章中简要介绍了该文件。我们已经知道,此文件在应用程序开始时执行,以注册和配置所需的服务和中间件来处理 HTTP 请求和响应管道。

实际上,由 ASP.NET Web API 模板创建的默认 Program.cs 文件与我们在第 1 章中看到的一样,因此我们不会在其中找到任何新内容。通过简要回顾一下,我们可以清楚地看到我们的 Web API 要使用的服务和中间件,如以下列表所示。

清单 2.2 Program.cs文件

var builder = WebApplication.CreateBuilder(args);
 
// Add services to the container.
 
builder.Services.AddControllers();              ❶
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
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();                      ❸
 
app.UseAuthorization();                         ❹
 
app.MapControllers();                           ❺
 
app.Run();

控制器服务和中间件

招摇服务和中间件

HTTP 到 HTTPS 重定向中间件

❹ ASP.NET 核心授权中间件

控制器服务和中间件

当我们在这里时,让我们借此机会添加一些有用的中间件,以协助我们更好地处理错误和异常。

异常处理

在Program.cs文件中找到以下代码:

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

将其替换为以下内容(以粗体标记的更改):

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/error");
}

正如我们所看到的,我们在 HTTP 管道中添加了新的中间件。仅当应用在开发环境中运行时,才会包含第一个添加项;第二个添加将仅存在于过渡和生产环境中。以下是此中间件的详细操作:

  • DeveloperExceptionPageMiddleware – 顾名思义,此中间件从 HTTP 管道捕获同步和异步异常,并生成一个 HTML 错误页面(开发人员异常页面),其中包含有关异常的有用信息,例如堆栈跟踪、查询字符串参数、Cookie 和标头。此信息可能会向潜在攻击者暴露配置设置或漏洞。出于这个缘由,我们只在开发环境中使用它,以便这些有用但可能有害的信息仅供开发人员使用。
  • 异常处理中间件 – 此中间件还处理 HTTP 级别的异常,但更适合非开发环境,由于它将所有相关的错误信息发送到可自定义的处理程序,而不是生成详细的错误响应并自动将其呈现给最终用户。

目前,尽管
DeveloperExceptionPageMiddleware 开箱即用,不需要任何额外的工作,但
ExceptionHandlingMiddleware 要求我们实现一个专用的处理程序。正如我们通过查看代码所看到的,我们已经传递了 /error string 参数,这意味着我们希望使用我们需要实现的专用 HTTP 路由来处理这些错误。

正如我们在第 1 章中已经知道的,我们有两种方法可以做到这一点:使用控制器或使用最小 API。让我们看看它们,然后选择最有效的一个。

使用控制器

让我们从基于控制器的方法开始。从 Visual Studio 的解决方案资源管理器中,执行以下步骤:

  1. 右键单击 MyBGList 项目的“控制器”文件夹,然后选择“添加>控制器”。将打开一个弹出窗口,要求我们选择要添加的控制器。
  2. 导航到左侧树视图中的“通用> API ”节点,选择“API 控制器 – 空”选项,然后单击“添加”按钮。
  3. 将新控制器命名为 ErrorController.cs,然后单击“确定”创建它。

我们将看到新的 ErrorController.cs 文件的内容:一个空类,我们可以用来添加我们的操作方法 — 具体来说,我们需要处理 /error/ 路由的操作方法,我们的
ExceptionHandlingMiddleware 将转发 HTTP 错误。下面是我们需要的操作方法的最小实现:

using Microsoft.AspNetCore.Mvc;
 
namespace MyBGList.Controllers
{
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]             ❶
        [HttpGet]                     ❷
        public IActionResult Error()
        {
            return Problem();         ❸
        }
    }
}

要处理的 HTTP 路由

要处理的 HTTP 方法

返回给调用方的 HTTP 响应

我们返回的 Problem() 方法是 ControllerBase 类(我们的 ErrorController 扩展)的一种方法,它生成 ProblemDetail 响应 — 一种机器可读的标准化格式,用于基于 RFC 7807 (
https://tools.ietf.org/html/rfc7807 指定 HTTP API 响应中的错误).简而言之,它是一个 JSON 文件,其中包含有关错误的一些有用信息:标题、详细信息、状态等。但是,由于我们在未指定任何参数的情况下调用 Problem() 方法,因此这些值将由 ASP.NET Core 使用从已引发的异常中获取的默认值自动设置。

使用最小 API

让我们看看如何使用最小 API 来实现一样的结果。在 Visual Studio 的解决方案资源管理器中,打开 Program.cs 文件,并在应用之前添加以下代码。MapControllers() 方法:

app.MapGet("/error", () => Results.Problem());

就是这样。实际上,Minimal API 似乎是这场比赛的明显赢家,由于它允许我们使用单行代码获得与我们的 ErrorController 一样的结果,而无需创建专用文件。这个结果应该不足为奇:此场景是死简单路由操作的完美示例,其中最小 API 大放异彩,而控制器更适合复杂任务。

在接下来的章节中,我们将看到许多基于控制器的方法将报复的场景。目前,我们不妨删除 ErrorController.cs 文件,并将最小 API 单行代码保留在 Program.cs 文件中。不过,第一,让我们花几分钟时间讨论一下,如果我们保持 ErrorController.cs 在原地会发生什么。

路由冲突

控制器和最小 API 可以毫无问题地存在于同一个项目中,因此开发人员可以两全其美。但它们应配置为处理不同的路由。如果他们共享一个终端节点,会发生什么情况?

如果我们记得在第1章中学到的内容,我们已经知道了答案:程序.cs文件中第一出现的中间件第一处理HTTP请求,并可能终止它,从而防止另一个请求发挥作用。这种行为超级好,不会在HTTP生命周期中造成任何重大问题,除了浪费在我们项目的代码库中有一个无用的实现。

在我们当前的方案中,由于我们放置了最小 API 的应用。MapGet() 方法就在应用程序之前。MapControllers() 方法,“死代码”受害者将是我们的 ErrorController.cs 文件。如果一切正常,我们为什么要删除该控制器?我们不能把它留在那里吗?

回答该问题的最佳方法是再次按 F5 并执行我们的应用,然后再删除 ErrorController.cs 文件。图 2.6 显示了我们应该得到什么。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图 2.6 SwaggerUI 错误 500(由于路由冲突)

正如我们所看到的,以前工作的 SwaggerUI 页面显示一个获取错误,由于它的数据源(自动生成的 swagger.json 文件在内部用于构建 UI)返回 HTTP 500 错误。如果我们复制该 URL (https:/ /localhost:40443/ swagger/v1/swagger.json) 并将其粘贴到我们 Web 浏览器的地址栏中,我们可以看到实际错误:

SwaggerGeneratorException: Conflicting method/path combination "GET error" 
for actions - MyBGList.Controllers.ErrorController.Error (MyBGList),HTTP: 
GET /error. Actions require a unique method/path combination for 
Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround.

这条错误消息将我们带到了问题的根源:我们有两个处理程序用于一样的方法/路径组合(GET /error),这会阻止 Swagger 正常工作。要解决此问题,我们可以做以下两件事之一:

  • 删除其中一个“重复”处理程序(控制器的 Error() 操作或最小 API 的 MapGet() 方法)。
  • 设置冲突操作解析程序以指示 Swagger 如何处理重复处理程序。

在这种情况下,删除 ErrorController.cs 文件或将其从项目中删除是更好的做法,由于我们无论如何都不想保留冗余代码。但是,如果我们出于某种缘由想要保留它,我们可以指示 Swagger 通过以下方式更改程序文件中的
SwaggerGeneratorMiddleware 配置来处理这种情况.cs(更新的代码以粗体标记):

builder.Services.AddSwaggerGen(opts =>
    opts.ResolveConflictingActions(apiDesc =>  apiDesc.First())
    );

我们告知 Swagger 通过始终采用找到的第一个并忽略其他冲突来解决与重复路由处理程序相关的所有冲突。但是,强烈提议不要使用此方法,由于它可以隐藏潜在的路由问题并导致意外结果。难怪错误消息称其为解决方法!

警告更一般地说,始终通过删除冗余(或错误)操作处理程序来解决路由冲突。设置框架(或中间件)以自动“解决”冲突几乎总是不好的做法,除非开发人员有足够的经验知道他们在做什么。

因此,在继续之前,我们能做的最好的事情是删除 ErrorController.cs 文件或将其从项目中排除(右键单击它,然后从解决方案资源管理器的上下文菜单中选择“从项目中排除”),这样 Swagger 就没有机会找到任何重复。

测试它

目前,我们已经为开发环境配置了开发人员例外页面,为生产环境配置了 Error() 操作,我们需要模拟实际错误。最快的方法是添加另一个引发异常的操作方法(或最小 API)。如果我们依旧有错误控制器,我们可以通过以下方式实现操作方法:

[Route("/error/test")]
[HttpGet]
public IActionResult Test()
{
    throw new Exception("test");
}

但是,由于我们选择从项目中删除或排除 ErrorController,因此我们可以将以下最小 API 单行代码放在程序.cs文件中,正下方是我们添加的其他 MapGet() 方法:

app.MapGet("/error/test", () => { throw new Exception("test"); });

然后单击“开始”按钮(或按 F5 键)并将 Web 浏览器指向 https://localhost:40443/error/test。如果我们正确执行了所有操作,我们应该看到由
DeveloperExceptionPageMiddleware 生成的开发人员异常页面,如图 2.7 所示。

使用 ASP.NET Core 构建 Web API:2 我们的第一个 Web API 项目

图 2.7 测试开发人员异常页面中间件

此结果是意料之中的,由于我们在开发环境中执行应用,由 launchSettings .json 文件中的 ASPNETCORE_ENVIRONMENT 变量指定。如果我们想测试
ExceptionHandlerMiddleware,我们需要做的就是将变量的值从开发更改为生产。

或者,我们可以充分利用我们添加到appsettings.json文件中的UseDeveloperExceptionPage键。实现此设置将允许我们在开发人员例外页面和 ExceptionHandler 之间切换,而无需更改应用程序的运行时环境。打开程序.cs文件,并替换代码

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/error");
}

使用此代码:

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
 
if (app.Configuration.GetValue<bool>("UseDeveloperExceptionPage"))   ❶
    app.UseDeveloperExceptionPage();                                 ❷
else
    app.UseExceptionHandler("/error");                                ❸

从 appsettings.json 文件中检索该文本值

如果为 TRUE,则使用
DeveloperExceptionPageMiddleware

如果为 FALSE,则改用
ExceptionHandlerMiddleware

目前将使用
ExceptionHandlerMiddleware 而不是
DeveloperExceptionPageMiddleware,由于在 appsetting.json 文件中,UseDeveloperExceptionPage 键的值设置为 false。我们可以立即按 F5,导航到 https://localhost:40443/error/test URL,并接收 ProblemDetail JSON 响应:

{
  "type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
  "title":"An error occurred while processing your request.",
  "status":500
}

注意此 JSON 输出在我们的方案中(目前)足够可行,由于它不会公开有关我们应用的潜在可利用信息。我们可以进一步自定义输出。我们可以使用方法重载支持的可选参数,将通用的“发生错误”标题替换为实际的 Exception 消息,为不同类型的错误提供不同的状态代码,等等。

目前我们已经完成这一系列测试,我们应该为开发环境重新启用
DeveloperExceptionPageMiddleware。我们可以打开appsettings .json文件并将UseDeveloperExceptionPage值从false更改为true,但这不是正确的做法。我们希望确保这样一个潜在的不安全页面只能被开发人员看到,记得吗?因此,重新启用它的正确方法是执行以下步骤:

  1. 打开应用设置。开发.json 文件。
  2. 添加一个 UseDeveloperExceptionPage 键(此文件中不存在该键)。
  3. 将键的值设置为 true。

以下是更新后的文件的外观(粗体换行):

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "UseDeveloperExceptionPage": true
}

目前我们放在应用程序设置中的值。每当我们的应用在开发运行时环境中启动时,Development.json 文件都会覆盖 appsettings.json 文件中的值,这正是我们想要的。

2.4.4 检查天气预报控制器

下一个要查看的文件是
WeatherForecastController.cs,我们可以在 /Controllers/ 文件夹中找到它。

注意按照 ASP.NET 约定,所有控制器类都必须驻留在项目的根级 /Controllers/ 文件夹中,并从
Microsoft.AspNetCore.Mvc.Controller 基类继承。

正如我们从第 1 章中知道的那样,控制器在 ASP.NET Core 中用于定义和分组处理 HTTP 请求(通过路由映射)并相应地返回 HTTP 响应的操作。如果我们看一下WeatherForecastController的源代码,我们可以看到它也不例外。此示例控制器旨在处理对 /WeatherForecast 路由的 HTTP GET 请求,并返回一个 HTTP 响应,其中包含一个包含五个 JSON 对象的数组,其中包含一些随机生成的日期、温度 C 和摘要属性值:

using Microsoft.AspNetCore.Mvc;
 
namespace MyBGList.Controllers
{
    [ApiController]                                                    ❶
    [Route("[controller]")]                                            ❷
    public class WeatherForecastController : ControllerBase
    {
           private static readonly string[] Summaries = new[] {
            "Freezing", "Bracing", "Chilly", "Cool", 
            "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };
 
        private readonly ILogger<WeatherForecastController> _logger;   ❸
 
        public WeatherForecastController
            (ILogger<WeatherForecastController> logger)
        {
            _logger = logger;                                          ❸
        }
 
        [HttpGet(Name = "GetWeatherForecast")]                         ❹
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5)
                .Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

添加特定于 API 的行为

默认路由规则

ILogger 实例(通过依赖注入实例化)

处理 HTTP GET 到 /WeatherForecast 的操作

如果我们尝试执行此代码,我们会得到以下结果:

[{
  date: "2021-12-03T02:04:31.5766653+01:00",
  temperatureC: 0,
  temperatureF: 32,
  summary: "Warm"
},
{
  date: "2021-12-04T02:04:31.5770138+01:00",
  temperatureC: 23,
  temperatureF: 73,
  summary: "Freezing"
},
{
  date: "2021-12-05T02:04:31.5770175+01:00",
  temperatureC: 40,
  temperatureF: 103,
  summary: "Freezing"
},
{
  date: "2021-12-06T02:04:31.5770178+01:00",
  temperatureC: 47,
  temperatureF: 116,
  summary: "Cool"
},
{
  date: "2021-12-07T02:04:31.577018+01:00",
  temperatureC: 36,
  temperatureF: 96,
  summary: "Mild"
}]

返回的对象是 C# WeatherForecast 类的 JSON 表明形式,该类在项目根文件夹的 WeatherForecast.cs 文件中定义。 正如我们通过查看其源代码所看到的,它是一个 POCO 类,其中包含一些可以轻松序列化为 JSON 输出的属性:

namespace MyBGList
{
    public class WeatherForecast
    {
        public DateTime Date { get; set; }
 
        public int TemperatureC { get; set; }
 
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
 
        public string? Summary { get; set; }
    }
}

注意 POCO 代表普通旧 CLR 对象,换句话说,一个没有依赖项、属性、基础结构问题、特殊类型或其他职责的普通类。

WeatherForecastController 和 WeatherForecast 示例类可用于了解 ASP.NET 控制器的工作原理,但它们不适合我们的具体方案。在处理与棋盘游戏相关的 API 时,我们不需要了解任何有关温度或预测的信息。出于这个缘由,我们将删除这些文件或将它们从项目中排除,就像我们之前对 ErrorController.cs 文件所做的那样,并将它们替换为更相关的示例。

2.4.5 添加棋盘游戏控制器

让我们从POCO类开始,它将取代以前的天气预报。在 Visual Studio 的解决方案资源管理器中,执行以下步骤(清单 2.3):

  1. 删除现有的天气预报.cs文件。
  2. 右键单击 MyBGList 项目的根文件夹,从上下文菜单中选择“添加新项>”,然后创建一个新的 BoardGame.cs 类文件。
  3. 用托管一些棋盘游戏数据的 POCO 类填充新文件。

清单 2.3 BoardGame.cs文件

namespace MyBGList
{
    public class BoardGame
    {
        public int Id { get; set; }
 
        public string? Name { get; set; }
 
        public int? Year { get; set; }
    }
}

这个新类依旧是一个示例,但它与我们选择的方案更一致!让我们对控制器执行一样的操作。在 Visual Studio 的解决方案资源管理器中,执行以下步骤(清单 2.4):

  1. 导航到 /Controllers/ 文件夹,然后删除现有的 WeatherForecastController.cs 文件。
  2. 右键单击 /Controllers/ 文件夹,从上下文菜单中选择“添加>控制器”,创建新的 API 控制器 – 空,并将新文件命名为 BoardGamesController.cs。
  3. 删除 /api/ 前缀,由于我们不需要它。
  4. 添加一个新的操作方法,以使用我们创建的 BoardGame POCO 类返回棋盘游戏数据数组。

清单 2.4 BoardGame.cs文件

using Microsoft.AspNetCore.Mvc;
 
namespace MyBGList.Controllers
{
    [Route("[controller]")]                   ❶
    [ApiController]
    public class BoardGamesController : ControllerBase
    {
        private readonly ILogger<BoardGamesController> _logger;
 
        public BoardGamesController(ILogger<BoardGamesController> logger)
        {
            _logger = logger;
        }
 
        [HttpGet(Name = "GetBoardGames")]     ❷
        public IEnumerable<BoardGame> Get()
        {
            return new[] {
                new BoardGame() {
                    Id = 1,
                    Name = "Axis & Allies",
                    Year = 1981
                },
                new BoardGame() {
                    Id = 2,
                    Name = "Citadels",
                    Year = 2000
                },
                new BoardGame() {
                    Id = 3,
 
                    Name = "Terraforming Mars",
                    Year = 2016
                }
            };
        }
    }
}

更新的路线模式

新的获取方法

就是这样。我们新的 BoardGamesController 将处理 /BoardGames 路由,并使用一个 JSON 数组进行响应,该数组包含过去 45 年左右发布的三款备受赞誉的棋盘游戏的一些相关示例信息。

我们的BoardGamesController的最小行为可以通过Minimal API通过几行代码轻松处理。下面是一个代码片段,我们可以将其放入 Program.cs 文件中,以从 Get() 操作方法获取一样的输出:

app.MapGet("/BoardGames", () => new[] {
    new BoardGame() {
        Id = 1,
        Name = "Axis & Allies",
        Year = 1981
    },
    new BoardGame() {
        Id = 2,
        Name = "Citadels",
        Year = 2000
    },
    new BoardGame() {
        Id = 3,
        Name = "Terraforming Mars",
        Year = 2016
    }
});

此示例只是一个示例 JSON 响应,它模拟了更复杂的行为,其中一般包括重大的数据检索。在接下来的章节中,我们将使用实体框架核心从数据库管理系统 (DBMS) 获取棋盘游戏数据,甚至可能为用户更新它。当我们处理这些类型的操作时,基于控制器的方法变得方便,甚至可能比最小 API 更方便。出于这个缘由,这次我们将保留控制器而不是更换它。

2.5 练习

建立对 ASP.NET 和Visual Studio的信心的最佳方法是,一旦我们了解了各种工具的工作原理,就立即练习使用它们。本节提供了一些有用的练习,使我们能够使用在本章中学到的技能进一步自定义我们的第一个 Web API 项目。每个练习都旨在修改单个文件,但通过完成所有练习,我们将能够实现一致的总体目标。

假设我们需要为一组选定的内部测试人员配置新的 MyBGList Web API,这些测试人员将能够通过一组给定的 TCP 端口访问我们的开发机器。以下是我们需要确保的细节的完整积压工作:

  1. 测试人员只能使用 55221 和 55222 TCP 端口。
  2. 测试人员只能使用 Kestrel Web 服务器。
  3. 用于测试应用的 Web 浏览器应从 BoardGamesController 的 Get() 操作方法返回的 BoardGames 的 JSON 列表开始
  4. 必须允许测试人员(如开发人员)访问 SwaggerUI 页面,但不能访问开发人员例外页面,他们应该无法访问该页面。
  5. 测试人员需要为每个棋盘游戏检索两个附加字段:最小玩家和最大玩家。这些字段应包含棋盘游戏支持的最小和最大玩家数。

提示如果您觉得大胆,请停止阅读此处,并在没有进一步协助的情况下开始练习(困难模式)。如果您对到目前为止所学的内容不太有信心,可以阅读以下部分,这些部分提供了所有相关步骤的一般指导,而不会泄露解决方案(宽松模式)。所有给定练习的解决方案都可以在 GitHub 的 /Chapter_02/Exercises/ 文件夹中找到。若要测试它们,请将 MyBGList 项目中的相关文件替换为该文件夹中的文件,然后运行应用。

2.5.1 luanchSettings.json

我们需要做的第一件事是确保测试人员能够通过给定的TCP端口访问本地机器。明智的做法是设置一个专用的运行时环境供他们使用。暂存环境似乎是完美的选择,由于它允许我们定义一些特定的配置设置,而无需更改生产和开发环境的配置,我们可能需要这些配置。我们可以通过更新launchSettings.json文件并通过以下方式为MyBGList项目配置Kestrel启动设置来执行这些任务:

  1. 将 TCP 端口 55221 用于 HTTP,将 55222 用于 HTTPS。
  2. 将运行时环境设置为“暂存”。
  3. 将起始终结点 URL 设置为由 BoardGamesController 的 Get() 操作方法处理的路由,以便 Web 浏览器在应用启动时自动显示该页面。

我们不需要更改 IIS Express 的设置,由于测试人员不会使用它。这些任务完成积压工作中的第 1、2 和 3 项。

2.5.2 appsettings.json

接下来要做的是为暂存运行时环境创建设置文件,并定义一些对所有环境都有效的默认行为,我们可以在需要时有条件地覆盖这些行为。以下是我们如何完成所有这些任务:

  1. 将新的 UseSwagger 配置设置添加到 MyBGList 应用设置,该设置对所有运行时环境都有效,值为 False。
  2. 将现有配置文件中一样的 UseSwagger 配置设置添加到开发环境中,值为 True。
  3. 为过渡环境创建新的配置文件,并覆盖设置,如下所示:
  4. 使用开发人员异常页面:假
  5. 使用招摇:真

这些任务还不会影响任何事情,但它们符合我们规范的第 4 项。

提示将暂存环境的 UseDeveloperExceptionPage 设置为 false 可能是多余的,由于该值已在通用 appsettings.json 文件中设置。但是,由于我们谈论的是包含潜在机密信息的页面,因此在给定环境中明确拒绝访问不会造成伤害。

2.5.3 Program.cs

目前我们有了正确的应用设置变量,我们可以根据应用的运行时环境,使用它们有条件地添加(或跳过)相关中间件。我们需要打开 Program.cs 文件并更改 SwaggerMiddleware 和 SwaggerUIMiddleware 的当前初始化策略,以确保仅在 UseSwagger 设置为 True 时使用它们。通过这样做,我们完成了积压的第 4 项。

2.5.4 BoardGame.cs

若要实现积压工作的第 5 项,我们需要向现有的 BoardGame POCO 类添加两个新属性。至于要使用的类型,最合适的选择是可为空的 int,由于我们用于 Year 属性;我们无法确定此类信息是否始终适用于所有棋盘游戏。

2.5.5 BoardGameController.cs

将这些属性添加到 BoardGame 类不足以在 JSON 文件中正确显示它们,除非我们希望它们始终为 null。由于我们目前正在处理示例数据,所以我们唯一能做的就是更新我们的 BoardGameController 的 Get() 方法并手动设置固定值。这项任务足以完成积压工作的第 5 项并完成练习。

我们剩下要做的就是选择 Kestrel 作为启动 Web 服务器,单击“开始”按钮(或按 F5)启动我们的 Web API 项目,看看会发生什么。如果我们正确执行了所有操作,我们的 Web 浏览器应该自动调用 https://localhost :55221/boardgames 端点并显示以下 JSON 响应:

[{
  "id":1,
  "name":"Axis & Allies",
  "year":1981,
  "minPlayers":2,
  "maxPlayers":5
},
{
  "id":2,
  "name":"Citadels",
  "year":2000,
  "minPlayers":2,
  "maxPlayers":8
},
{
  "id":3,
  "name":"Terraforming Mars",
  "year":2016,
  "minPlayers":1,
  "maxPlayers":5
}]

如果我们取得了这个结果,我们就准备好继续前进了。

总结

  • 若要创建 ASP.NET 核心应用,我们需要下载并安装 .NET Core SDK、.NET Core 运行时和 ASP.NET Core 运行时(除非您选择了自动执行这些操作的 IDE,例如 Visual Studio)。
  • 我们还应该为自己提供一个合适的IDE,如Visual Studio:一个全面的Windows和macOS开发解决方案,我们将在本书中使用。Visual Studio 可以显著提高我们的工作效率,并协助我们标准化开发过程,这要归功于任务运行程序、包管理器、集成源代码管理和语法突出显示等内置功能。
  • Visual Studio 包含许多有用的模板,我们可以将其用作创建应用程序的样板,包括 ASP.NET Core Web API 模板,这是启动我们的 MyBGList Web API 项目的完美选择。
  • Visual Studio 的 ASP.NET Core Web API 模板附带了一小组自动生成的文件:
    • 用于设置服务和中间件的启动文件(程序.cs)
    • 用于使用开发 Web 服务器启动应用的设置文件 (launchSettings.json)
    • 一组配置文件,用于存储应用特定于环境的设置 (appsettings.json)
    • 用于模拟数据对象的 POCO 类 (天气预报.cs)
    • 一个控制器类,可以使用一些示例 JSON 数据(天气预报控制器.cs响应简单的 HTTP 请求)
  • 在简要回顾模板文件和一些小的代码更改以了解它们的工作原理后,我们可以开始用一些与棋盘游戏相关的类替换内置的天气预报示例类。
  • 在继续之前,通过一些练习来测试我们获得的知识可能会很有用,模拟来自客户的一系列请求(积压项)。
    • 根据我们的信心,我们可以尝试在没有任何提议的情况下实现它们或遵循一些高级指导。
    • 无论选择的复杂程度如何,进行此类练习都是测试我们当前技能并为即将到来的主题做准备的好方法。
© 版权声明

相关文章

暂无评论

none
暂无评论...