使用支持创建、读取、更新、删除 (CRUD) 操作的 ASP.NET Core 控制器创建 RESTful 服务。
ASP.NET Core中的REST
当你浏览网页时,Web 服务器将使用 HTML、CSS 和 JavaScript 与浏览器通信。 如果你与页面进行某种交互,例如提交登录窗体或选择购买按钮,浏览器会将信息发送回 Web 服务器。
同样,Web 服务器可以使用 Web 服务与各种各样的客户端(浏览器、移动设备、其他 Web 服务器等)通信。 API 客户端通过 HTTP 与服务器通信,两者都使用 JSON 或 XML 等数据格式来交换信息。 API 通常供单页应用程序 (SPA) 用于在 Web 浏览器中执行大部分用户界面逻辑。 与 Web 服务器的通信主要是通过 Web API 进行的。
REST:用于使用HTTP生成API的常见模式
表述性状态转移 (REST) 是一种用于生成 Web 服务的体系结构样式。 REST 请求是通过 HTTP 发出的。 它们使用 Web 浏览器用于检索网页和将数据发送到服务器的相同 HTTP 谓词。 谓词如下:
GET
:从 Web 服务检索数据。POST
:在 Web 服务上创建新的数据项。PUT
:更新 Web 服务上的数据项。PATCH
:通过描述有关如何修改项的一组说明,更新 Web 服务上的数据项。 本模块中的示例应用程序不使用此谓词。DELETE
:删除 Web 服务上的数据项。
遵循 REST 的 Web 服务 API 称为 RESTful API。 它们通过以下方法进行定义:
- 一个基 URI。
- HTTP 方法,如
GET
、POST
、PUT
、PATCH
或DELETE
。 - 数据的媒体类型,例如 JavaScript 对象表示法 (JSON) 或 XML。
API 通常需要为几种不同但相关的事物提供服务。 例如,我们的披萨 API 可以管理披萨、客户和订单。 我们使用路由将 URI 映射到代码中的逻辑分区,以便将对 https://localhost:5000/pizza 的请求路由到 PizzaController
。 对 https://localhost:5000/order 的请求将被路由到 OrderController
。
在ASP.NET Core中创建API的好处
借助 ASP.NET,可以使用相同的框架和模式来生成网页和服务。 这意味着可以在同一个项目中重用模型类和验证逻辑,甚至还可以并行为网页和服务提供服务。 此方法有很多优点:
-
简单的序列化:ASP.NET 设计用于提供新式 Web 体验。 终结点会自动将类序列化为格式正确的现成 JSON。 不需要特殊配置。 当然,你可以为具有独特要求的终结点自定义序列化。
-
身份验证和授权:出于安全原因,API 终结点内置了对行业标准 JSON Web 令牌 (JWT) 的支持。 基于策略的授权使你可以灵活地在代码中定义强大的访问控制规则。
-
与代码一起路由:ASP.NET 让你可以使用属性来定义与代码内联的路由和谓词。 请求路径、查询字符串和请求正文中的数据将自动绑定到方法参数。
-
默认使用 HTTPS:HTTPS 是新式专业 Web API 的一个重要组成部分。 它依赖于端到端加密来提供隐私保护,并有助于确保 API 调用不会在客户端和服务器之间被截获和更改。
ASP.NET 提供对 HTTPS 的现成支持。 它自动生成测试证书,并将其轻松导入以启用本地 HTTPS。 因此,在发布应用程序之前,可以安全地运行和调试应用程序。
与.NET应用共享代码和知识
使用 .NET 技能和生态系统,与使用 .NET 生成的其他应用(包括移动应用、Web 应用、桌面应用和服务)共享 Web API 的逻辑。
使用.NET HTTP REPL测试Web API
当你开发传统网站时,你通常会在 Web 浏览器中查看和测试工作成果。 Web API 接受并返回数据而不是 HTML,因此 Web 浏览器不是最佳的 Web API 测试工具。
用于浏览 Web API 以及与其交互的最简单选项之一是 .NET HTTP REPL。 REPL 表示读取-求值-打印循环 (read-evaluate-print loop)。 这是一种简单而常用的交互式命令行环境构建方法。 在下一单元中,你将创建一个简单的 Web API,然后使用 .NET HTTP REPL 与其交互。
创建Web API项目
通过在首选终端中运行以下命令,确保已安装 .NET 6.0:
dotnet --list-sdks
确保列出了以 6
开头的版本。
创建并浏览Web API项目
为了设置 .NET 项目以与 Web API 配合工作,我们将使用 Visual Studio Code。 Visual Studio Code 包含一个集成终端,这使创建新项目变得很简单。
-
在 Visual Studio Code 中,选择“文件”>“打开文件夹”。
-
在所选位置创建名为 ContosoPizza 的新文件夹,然后单击“选择文件夹”。
-
从主菜单中选择“视图”>“终端”,以便从 Visual Studio Code 中打开集成终端。
-
在终端窗口中,输入以下命令:
dotnet new webapi -f net6.0
此命令将创建使用控制器的基本 Web API 项目的文件,以及一个名为 ContosoPizza.csproj 的 C# 项目文件,该文件将返回天气预报列表。
默认使用 https
保护 Web API 项目。
Visual Studio Code 可能会提示你添加资产以调试项目。 在对话框中,选择“是”。
此命令使用别名为 webapi 的 ASP.NET Core 项目模板来创建基于 C# 的 Web API 项目的基架。 将创建一个 ContosoPizza 目录。 此目录包含在 .NET 上运行的 ASP.NET Core 项目。 项目名称与 ContosoPizza 目录名称匹配。
你现在应可以访问以下文件:
-| Controllers
-| obj
-| Properties
-| appsettings.Development.json
-| appsettings.json
-| ContosoPizza.csproj
-| Program.cs
-| WeatherForecast.cs
检查以下文件和目录:
名称 | 说明 |
---|---|
Controllers/ | 包含公共方法公开为 HTTP 终结点的类 |
Program.cs | 配置服务和应用的 HTTP 请求管道,并包含应用的托管入口点 |
ContosoPizza.csproj | 包含项目的配置元数据 |
生成并测试Web API
在命令行界面中运行以下 .NET Core CLI 命令:
dotnet run
上述命令:
- 在当前目录中找到项目文件。
- 检索并安装此项目所需的任何项目依赖项。
- 编译项目代码。
- 使用 ASP.NET Core Kestrel Web 服务器将 Web API 托管在 HTTP 和 HTTPS 终结点中。
创建项目时,将为 HTTP 选择 5000 到 5300 端口,为 HTTPS 选择 7000 到 7300 端口。 通过编辑项目的 launchSettings.json 文件,可以轻松更改开发过程中使用的端口。 本模块使用以 https
开头的安全 localhost
URL。
最后会看到类似以下内容的输出,表明应用正在运行:
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7294
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5118
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
如果在自己的计算机上运行此应用,可以将浏览器指向输出中显示的 HTTPS 链接(在上例中为 https://localhost:7294
)以查看生成的页面。 请记住此端口,因为在使用 {PORT}
的模块中将一直使用它。
打开 Web 浏览器并转到:
https://localhost:{PORT}/weatherforecast
以下输出显示了返回的 JSON 的摘录:
[
{
"date": "2021-11-09T20:36:01.4678814+00:00",
"temperatureC": 33,
"temperatureF": 91,
"summary": "Scorching"
},
{
"date": "2021-11-09T20:36:01.4682337+00:00",
"temperatureC": -8,
"temperatureF": 18,
"summary": "Cool"
},
// ...
]
[
{
"date":"2022-08-12T14:52:39.9538815+08:00",
"temperatureC":-6,
"temperatureF":22,
"summary":"Balmy"
},
{
"date":"2022-08-13T14:52:39.9549008+08:00",
"temperatureC":23,
"temperatureF":73,
"summary":"Bracing"
},
{
"date":"2022-08-14T14:52:39.954912+08:00",
"temperatureC":-16,
"temperatureF":4,
"summary":"Balmy"
},
{
"date":"2022-08-15T14:52:39.9549125+08:00",
"temperatureC":41,
"temperatureF":105,
"summary":"Freezing"
},
{
"date":"2022-08-16T14:52:39.9549128+08:00",
"temperatureC":-5,
"temperatureF":24,
"summary":"Scorching"
}
]
通过从主菜单中选择“终端”>“新终端”,从 Visual Studio Code 中打开新的集成终端。
然后,运行以下命令:
dotnet tool install -g Microsoft.dotnet-httprepl
前面的命令将安装 .NET HTTP REPL 命令行工具,你将使用它向 Web API 发出 HTTP 请求。
通过运行以下命令连接到 Web API:
httprepl https://localhost:{PORT}
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
Telemetry
---------
The .NET tools collect usage data in order to help us improve your experience. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_HTTPREPL_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.
Read more about HttpRepl telemetry: https://aka.ms/httprepl-telemetry
Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry
(Disconnected)>
或者,在 HttpRepl
运行时随时运行以下命令:
(Disconnected)> connect https://localhost:{PORT}
(Disconnected)> connect https:/localhost:7095If specified, the root address must be a valid absolute url, including scheme
(Disconnected)> connect https://localhost:7095
Using a base address of https://localhost:7095/
Using OpenAPI description at https://localhost:7095/swagger/v1/swagger.json
For detailed tool info, see https://aka.ms/http-repl-doc
https://localhost:7095/>
通过运行以下命令浏览可用的终结点:
ls
前面的命令将检测连接的终结点上所有可用的 API。 此命令应该显示以下输出:
https://localhost:{PORT}/> ls
. []
WeatherForecast [GET]
https://localhost:7095/> ls
. []
WeatherForecast [GET]
https://localhost:7095/>
运行以下命令以转到 WeatherForecast
终结点:
cd WeatherForecast
前面的命令将显示 WeatherForecast
终结点的可用 API 的输出:
https://localhost:{PORT}/> cd WeatherForecast
/WeatherForecast [GET]
https://localhost:7095/> cd WeatherForecast
/WeatherForecast [GET]
https://localhost:7095/WeatherForecast>
使用以下命令在 HttpRepl
中发出 GET
请求:
get
前面的命令发出的 GET
请求类似于转到浏览器中的终结点:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Apr 2021 17:31:43 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"date": 4/3/2021 10:31:44 AM,
"temperatureC": 13,
"temperatureF": 55,
"summary": "Sweltering"
},
{
"date": 4/4/2021 10:31:44 AM,
"temperatureC": -13,
"temperatureF": 9,
"summary": "Warm"
},
// ..
]
https://localhost:7095/WeatherForecast> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 07:11:21 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
"temperatureC": -16,
"temperatureF": 4,
"summary": "Mild"
},
{
"date": 2022/8/15 15:11:21,
"temperatureC": 30,
"temperatureF": 85,
"summary": "Bracing"
},
{
"date": 2022/8/16 15:11:21,
"temperatureC": 17,
"temperatureF": 62,
"summary": "Mild"
}
]
https://localhost:7095/WeatherForecast>
使用以下命令结束当前的 HttpRepl
会话:
exit
在 Visual Studio Code 中返回到下拉列表中的 dotnet
终端。 通过选择键盘上的 CTRL+C 关闭 Web API。
已创建了 Web API,将对其进行修改以满足披萨 Web API 的需求。
ASP.NET Core Web API控制器
WeatherController类的代码:
using Microsoft.AspNetCore.Mvc;
namespace ContosoPizza.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();
}
}
基类:ControllerBase
控制器是一个公共类,具有一个或多个称为“操作”的公共方法。 按照惯例,控制器放在项目根目录的 Controllers 目录中。 操作通过路由被公开为 HTTP 终结点。 因此,对 https://localhost:{PORT}/weatherforecast
的 HTTP GET
请求将执行 WeatherForecastController
类的 Get()
方法。
首先要注意的是,此类继承自 ControllerBase
基类。 这个基类提供了许多用于处理 HTTP 请求的标准功能,让你可以专注于应用程序的特定业务逻辑。
如果在 ASP.NET Core 中开发过 Razor Pages 或模型-视图-控制器 (MVC) 体系结构,那么已使用过 Controller
类。 不要通过从 Controller
类派生来创建 Web API 控制器。 Controller
派生自 ControllerBase
,并添加了对视图的支持,因此它用于处理网页,而不是 Web API 请求。
API控制器类属性
有两个重要属性应用到了 WeatherForecastController
,如以下代码所示:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
[ApiController] 启用固定行为,使生成 Web API 更加容易。 一些行为包括参数源推理、将属性路由作为一项要求以及模型验证错误处理增强功能。
[Route] 定义路由模式 [controller]。 [controller] 令牌将替换为控制器的名称(不区分大小写,无 Controller 后缀)。 此控制器处理对 https://localhost:{PORT}/weatherforecast 的请求。
路由可能包含静态字符串,如 api/[controller]
中所示。 在此中,此控制器将处理对 https://localhost:{PORT}/api/weatherforecast
的请求。
使用Get()方法提供天气预报结果
WeatherForecastController
包括由 [HttpGet(Name = "GetWeatherForecast")]
属性指定的单个控制器操作。 此属性将 HTTP GET
请求路由到 public IEnumerable<WeatherForecast> Get()
方法。 因此,看到了向 https://localhost:{PORT}/weatherforecast
发出请求会导致返回天气预报结果。
获取返回的 WeatherForecast
的完整列表。 GET
操作还允许通过传递标识符来检索单个项。 在 ASP.NET 中,可以通过使用 [HttpGet("{id}")]
属性来实现此目的。
添加数据存储
创建披萨模型
运行以下命令以创建 Models 文件夹:
mkdir Models
在 Visual Studio Code 中选择 Models 文件夹,并添加名为 Pizza.cs 的新文件。
项目根目录现在包含一个带有空 Pizza.cs 文件的 Models 目录。 目录名称 Models 是一种约定。 目录名称来自 Web API 使用的模型-视图-控制器体系结构。
将以下代码添加到 Models/Pizza.cs 以定义披萨。 保存更改。
namespace ContosoPizza.Models;
public class Pizza
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsGlutenFree { get; set; }
}
添加数据服务
运行以下命令以创建 Services 文件夹:
mkdir Services
在 Visual Studio Code 中选择该文件夹,并添加名为 PizzaService.cs 的新文件。
将以下代码添加到 Services/PizzaService.cs,以创建内存中披萨数据服务。 保存所做更改。
using ContosoPizza.Models;
namespace ContosoPizza.Services;
public static class PizzaService
{
static List<Pizza> Pizzas { get; }
static int nextId = 3;
static PizzaService()
{
Pizzas = new List<Pizza>
{
new Pizza { Id = 1, Name = "Classic Italian", IsGlutenFree = false },
new Pizza { Id = 2, Name = "Veggie", IsGlutenFree = true }
};
}
public static List<Pizza> GetAll() => Pizzas;
public static Pizza? Get(int id) => Pizzas.FirstOrDefault(p => p.Id == id);
public static void Add(Pizza pizza)
{
pizza.Id = nextId++;
Pizzas.Add(pizza);
}
public static void Delete(int id)
{
var pizza = Get(id);
if(pizza is null)
return;
Pizzas.Remove(pizza);
}
public static void Update(Pizza pizza)
{
var index = Pizzas.FindIndex(p => p.Id == pizza.Id);
if(index == -1)
return;
Pizzas[index] = pizza;
}
}
默认情况下,此服务提供一个简单的内存中数据缓存服务,其中包含两个披萨。 我们的 Web API 将使用该服务进行演示。 如果先停止再启动 Web API,内存中数据缓存将重置为 PizzaService
的构造函数中的两个默认披萨。
生成Web API项目
运行以下命令以生成应用:
dotnet build
生成成功,且没有任何警告。
添加控制器
控制器是一个公共类,具有一个或多个称为“操作”的公共方法。 按照惯例,控制器放在项目根目录的 Controllers 目录中。 这些操作在 Web API 控制器内公开为 HTTP 终结点。
添加控制器
在 Visual Studio Code 中选择 Controllers 文件夹,并添加名为 PizzaController.cs 的新文件。
系统会在 Controllers 目录中创建名为 PizzaController.cs 的空类文件。 目录名称 Controllers 是一种约定。 目录名称来自 Web API 使用的模型-视图-控制器体系结构。
按照惯例,控制器类名称后缀为 Controller。
将以下代码添加到 Controllers/PizzaController.cs。 保存所做更改。
using ContosoPizza.Models;
using ContosoPizza.Services;
using Microsoft.AspNetCore.Mvc;
namespace ContosoPizza.Controllers;
[ApiController]
[Route("[controller]")]
public class PizzaController : ControllerBase
{
public PizzaController()
{
}
// GET all action
// GET by Id action
// POST action
// PUT action
// DELETE action
}
如前文所述,此类派生自 ControllerBase
,后者是 ASP.NET Core 中用于处理 HTTP 请求的基类。 它还包含你已经了解的两个标准属性,即 [ApiController]
和 [Route]
。 如前文所述,[Route]
属性定义了到 [controller]
令牌的映射。 由于此控制器类名为 PizzaController
,因此该控制器处理对 https://localhost:{PORT}/pizza
的请求。
获取所有披萨
需要实现的第一个 REST 谓词是 GET
,使用该谓词,客户端可以从 API 获取所有披萨。 你可以使用内置 [HttpGet]
属性来定义将从服务返回披萨的方法。
将 Controllers/PizzaController.cs 中的 // GET all action
注释替换为以下代码:
[HttpGet]
public ActionResult<List<Pizza>> GetAll() =>
PizzaService.GetAll();
上一个操作:
- 仅响应 HTTP
GET
谓词,如[HttpGet]
属性所示。 - 查询服务以获取所有披萨,并通过
Content-Type
的值application/json
自动返回数据。
检索单种披萨
客户端可能还需要请求获取特定披萨而非整个列表的相关信息。 你可以实现另一个 GET
操作,此操作需要 id
参数。 你可以使用内置 [HttpGet("{id}")]
属性来定义将从服务返回披萨的方法。 路由逻辑将 [HttpGet]
(没有 id
)和 [HttpGet("{id}")]
(具有 id
)注册为两个不同的路由。 然后,你可以编写一个单独的操作来检索单个项。
将 Controllers/PizzaController.cs 中的 // GET by Id action
注释替换为以下代码:
[HttpGet("{id}")]
public ActionResult<Pizza> Get(int id)
{
var pizza = PizzaService.Get(id);
if(pizza == null)
return NotFound();
return pizza;
}
上一个操作:
- 仅响应 HTTP
GET
谓词,如[HttpGet]
属性所示。 - 要求
pizza/
之后的 URL 段中包含id
参数的值。 请记住,控制器级别的/pizza
属性定义了[Route]
模式。 - 查询数据库以获取与所提供的
id
参数匹配的披萨。
上述操作中使用的每个 ActionResult
实例都映射到下表中对应的 HTTP 状态代码:
ASP.NET Core 操作结果 | HTTP 状态代码 | 说明 |
---|---|---|
Ok 为隐式 | 200 | 内存中缓存中存在与所提供的 id 参数匹配的产品。该产品包含在由 accept HTTP 请求标头中所定义的媒体类型(默认情况下为 JSON)的响应正文中。 |
NotFound | 404 | 内存中缓存中不存在与所提供的 id 参数匹配的产品。 |
生成并测试控制器
运行以下命令,生成并启动 Web API:
dotnet run
打开现有 httprepl
终端,或者通过从主菜单中选择“终端”>“新终端”,从 Visual Studio Code 中打开新的集成终端。
通过运行以下命令连接到 Web API:
httprepl https://localhost:{PORT}
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\ASP.NET\ContosoPizza> httprepl https://localhost:7095
(Disconnected)> connect https://localhost:7095
Using a base address of https://localhost:7095/
Using OpenAPI description at https://localhost:7095/swagger/v1/swagger.json
For detailed tool info, see https://aka.ms/http-repl-doc
https://localhost:7095/>
或者,在 HttpRepl
运行时随时运行以下命令:
(Disconnected)> connect https://localhost:{PORT}
https://localhost:7095/> connect https://localhost:7095
Using a base address of https://localhost:7095/
Using OpenAPI description at https://localhost:7095/swagger/v1/swagger.json
For detailed tool info, see https://aka.ms/http-repl-doc
https://localhost:7095/>
若要查看新的可用 Pizza
终结点,请运行以下命令:
ls
前面的命令将检测连接的终结点上所有可用的 API。 此命令应显示以下代码:
https://localhost:{PORT}/> ls
. []
Pizza [GET]
WeatherForecast [GET]
https://localhost:7095/> ls
. []
Pizza [GET]
WeatherForecast [GET]
https://localhost:7095/>
运行以下命令以转到 Pizza
终结点:
cd Pizza
前面的命令将显示 Pizza
终结点的可用 API 的输出:
https://localhost:{PORT}/> cd Pizza
/Pizza [GET]
https://localhost:7095/> cd Pizza
/Pizza [GET]
https://localhost:7095/Pizza>
使用以下命令在 HttpRepl
中发出 GET
请求:
get
前面的命令将返回 json
中所有披萨的列表:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Apr 2021 21:55:53 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"id": 1,
"name": "Classic Italian",
"isGlutenFree": false
},
{
"id": 2,
"name": "Veggie",
"isGlutenFree": true
}
]
https://localhost:7095/Pizza> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 08:27:08 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"id": 1,
"name": "Classic Italian",
"isGlutenFree": false
},
{
"id": 2,
"name": "Veggie",
"isGlutenFree": true
}
]
https://localhost:7095/Pizza>
若要查询单个披萨,可以使用以下命令发出 GET
请求,但传入 id
参数:
get 1
前面的命令返回 Classic Italian
,输出结果如下:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Apr 2021 21:57:57 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 1,
"name": "Classic Italian",
"isGlutenFree": false
}
https://localhost:7095/Pizza> get 1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 08:28:33 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 1,
"name": "Classic Italian",
"isGlutenFree": false
}
https://localhost:7095/Pizza>
我们的 API 还可以处理项不存在的情况。 使用以下命令再次调用该 API,但传入一个无效的披萨 id
参数:
get 5
前面的命令返回 404 Not Found
错误,输出结果如下:
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8
Date: Fri, 02 Apr 2021 22:03:06 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
"title": "Not Found",
"status": 404,
"traceId": "00-ec263e401ec554b6a2f3e216a1d1fac5-4b40b8023d56762c-00"
}
https://localhost:7095/Pizza> get 5
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8
Date: Thu, 11 Aug 2022 08:31:12 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
"title": "Not Found",
"status": 404,
"traceId": "00-56f21d7a689be57786a903052827021b-16cec3a02679e590-00"
}
https://localhost:7095/Pizza>
在 Visual Studio Code 中,返回到下拉列表中的 dotnet
终端,并通过选择键盘上的 CTRL+C 来关闭 Web API。
现在已经完成了 GET
谓词的实现。
ASP.NET Core中的CRUD操作
披萨服务支持对披萨列表执行 CRUD 操作。 这些操作是通过 HTTP 谓词执行的,它们通过 ASP.NET Core 属性进行映射。HTTP GET
谓词用于从服务检索一个或多个项。 此类操作使用 [HttpGet]
属性进行批注。
下表显示了披萨服务实现的四个操作的映射:
HTTP 操作谓词 | CRUD 操作 | ASP.NET Core 属性 |
---|---|---|
GET | 读取 | [HttpGet] |
POST | 创建 | [HttpPost] |
PUT | 更新 | [HttpPut] |
DELETE | 删除 | [HttpDelete] |
已经了解了 GET
操作的工作原理。 详细了解一下 POST
、PUT
和 DELETE
操作。
POST
为了使用户能够将新项添加到终结点,必须通过使用 [HttpPost]
属性来实现 POST
操作。 将项作为参数传递入方法时,ASP.NET Core 会自动将发送到终结点的任何应用程序/JSON 转换为填充的 .NET Pizza
对象。
以下是 Create
方法的方法签名,实现该方法:
[HttpPost]
public IActionResult Create(Pizza pizza)
{
// This code will save the pizza and return a result
}
[HttpPost]
属性将通过使用 Create()
方法映射发送到 http://localhost:5000/pizza
的 HTTP POST
请求。 此方法并不像我们在 Get()
方法中看到的那样返回披萨列表,而是返回 IActionResult
响应。
IActionResult
可让客户端知道请求是否成功,并提供新创建的披萨的 ID。 IActionResult
通过使用标准 HTTP 状态代码来完成该操作,因此,无论客户端使用哪种语言或者在哪个平台上运行,它都能轻松地与客户端集成。
ASP.NET Core 操作结果 | HTTP 状态代码 | 说明 |
---|---|---|
CreatedAtAction | 201 | 已将披萨添加到内存中缓存。 该披萨包含在由 accept HTTP 请求标头中所定义的媒体类型(默认情况下为 JSON)的响应正文中。 |
BadRequest 为隐式 | 400 | 请求正文的 pizza 对象无效。 |
幸运的是,ControllerBase
具有一些实用工具方法,这些方法可创建适当的 HTTP 响应代码和消息。
PUT
修改或更新库存中的披萨类似于已实现的 POST 方法。 但除了需要更新的 Pizza
对象之外,它还使用 [HttpPut]
属性并采用 id
参数:
[HttpPut("{id}")]
public IActionResult Update(int id, Pizza pizza)
{
// This code will update the pizza and return a result
}
上述操作中使用的每个 ActionResult
实例都映射到下表中对应的 HTTP 状态代码:
ASP.NET Core 操作结果 | HTTP 状态代码 | 说明 |
---|---|---|
NoContent | 204 | 已在内存中缓存中更新了披萨。 |
BadRequest | 400 | 请求正文的 Id 值与路由的 id 值不匹配。 |
BadRequest 为隐式 | 400 | 请求正文的 Pizza 对象无效。 |
DELETE
可实现的一种更简单的操作是 DELETE
操作,该操作只接受要从内存中缓存中删除的披萨的 id
参数:
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
// This code will delete the pizza and return a result
}
上述操作中使用的每个 ActionResult
实例都映射到下表中对应的 HTTP 状态代码:
ASP.NET Core 操作结果 | HTTP 状态代码 | 说明 |
---|---|---|
NoContent | 204 | 已从内存中缓存中删除了披萨。 |
NotFound | 404 | 内存中缓存中不存在与所提供的 id 参数匹配的披萨。 |
实现CRUD操作
扩展 Web API 控制器,添加在库存中创建 (POST
)、更新 (PUT
) 和删除 (DELETE
) 披萨的功能。
添加披萨
使用 POST
方法可通过 Web API 添加披萨。
使用以下代码替换 Controllers/PizzaController.cs 中的 // POST action
注释:
[HttpPost]
public IActionResult Create(Pizza pizza)
{
PizzaService.Add(pizza);
return CreatedAtAction(nameof(Create), new { id = pizza.Id }, pizza);
}
上一个操作:
- 仅响应 HTTP
POST
谓词,如[HttpPost]
属性所示。 - 将请求正文的
Pizza
对象插入到内存中缓存。
由于控制器使用 [ApiController]
属性进行批注,因此意味着将在请求正文中找到 Pizza
参数。
CreatedAtAction
方法调用中的第一个参数表示操作名称。 nameof
关键字用于避免对操作名称进行硬编码。 CreatedAtAction
使用操作名称来生成 location
HTTP 响应标头,该标头包含新创建的披萨的 URL,如上一个单元中介绍。
修改披萨
使用 PUT
方法可通过 Web API 更新披萨。
使用以下代码替换 Controllers/PizzaController.cs 中的 // PUT action
注释:
[HttpPut("{id}")]
public IActionResult Update(int id, Pizza pizza)
{
if (id != pizza.Id)
return BadRequest();
var existingPizza = PizzaService.Get(id);
if(existingPizza is null)
return NotFound();
PizzaService.Update(pizza);
return NoContent();
}
上一个操作:
- 仅响应 HTTP PUT 谓词,如
[HttpPut]
属性所示。 - 要求
pizza/
之后的 URL 段中包含id
参数的值。 - 返回
IActionResult
,因为在运行时之前,ActionResult
返回类型未知。BadRequest
、NotFound
和NoContent
方法分别返回BadRequestResult
、NotFoundResult
和NoContentResult
类型。
由于控制器批注通过 [ApiController]
属性进行,因此暗示着将在请求正文中找到 Pizza
参数。
删除披萨
使用 DELETE
方法可通过 Web API 删除披萨。
使用以下代码替换 Controllers/PizzaController.cs 中的 // DELETE action
注释:
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var pizza = PizzaService.Get(id);
if (pizza is null)
return NotFound();
PizzaService.Delete(id);
return NoContent();
}
上一个操作:
- 仅响应 HTTP
DELETE
谓词,如[HttpDelete]
属性所示。 - 要求
pizza/
之后的 URL 段中包含id
参数的值。 - 返回
IActionResult
,因为在运行时之前,ActionResult
返回类型未知。NotFound
和NoContent
方法分别返回NotFoundResult
和NoContentResult
类型。 - 查询内存中缓存以获取与所提供的
id
参数匹配的披萨。
生成并运行完成的Web API
运行以下命令,生成并启动 Web API:
dotnet run
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\ASP.NET\ContosoPizza> dotnet run
正在生成...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7095
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5023
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\ASP.NET\ContosoPizza\
重新打开现有 httprepl
终端,或者通过从主菜单中选择“终端”>“新终端”,从 Visual Studio Code 中打开新的集成终端。
如果打开了新终端,请通过运行以下命令连接到 Web API:
httprepl https://localhost:{PORT}
或者,在 HttpRepl
运行时随时运行以下命令:
(Disconnected)> connect https://localhost:{PORT}
运行以下命令以转到 Pizza
终结点:
cd Pizza
运行以下命令,查看对披萨 API 执行的新操作:
ls
前面的命令将显示 Pizza
终结点的可用 API 的输出:
https://localhost:{PORT}/Pizza> ls
. [GET|POST]
.. []
{id} [GET|PUT|DELETE]
https://localhost:7095/> cd Pizza
/Pizza [GET|POST]
https://localhost:7095/Pizza> ls
. [GET|POST]
.. []
{id} [GET|PUT|DELETE]
https://localhost:7095/Pizza>
使用以下命令发出 POST
请求,在 HttpRepl
中添加新的披萨:
post -c "{"name":"Hawaii", "isGlutenFree":false}"
前面的命令将返回所有披萨的列表:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Apr 2021 23:23:09 GMT
Location: https://localhost:{PORT}/Pizza?id=3
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 3,
"name": "Hawaii",
"isGlutenFree": false
}
https://localhost:7095/Pizza> post -c "{"name":"Hawaii", "isGlutenFree":false}"
No matching command found
Execute 'help' to see available commands
https://localhost:7095/Pizza>
https://localhost:7095/Pizza> post -c "{"name":"Veggie","isGlutenFree":true}"
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 09:06:44 GMT
Location: https://localhost:7095/Pizza?id=3
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 3,
"name": "Veggie",
"isGlutenFree": true
}
https://localhost:7095/Pizza>
使用以下命令发出 PUT
请求,将新的 Hawaii
披萨更新为 Hawaiian
披萨:
put 3 -c "{"id": 3, "name":"Hawaiian", "isGlutenFree":false}"
前面的命令将返回以下输出,指示操作成功:
HTTP/1.1 204 No Content
Date: Fri, 02 Apr 2021 23:23:55 GMT
Server: Kestrel
https://localhost:7095/Pizza> put 3 -c "{"id": 3, "name":"Hawaiian", "isGlutenFree":false}"
No matching command found
Execute 'help' to see available commands
https://localhost:7095/Pizza>
https://localhost:7095/Pizza> get 3
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 09:10:42 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 3,
"name": "Veggie",
"isGlutenFree": true
}
https://localhost:7095/Pizza> put 3 -c "{"id":3,"name":"Veggie1","isGlutenFree":true}"
HTTP/1.1 204 No Content
Date: Thu, 11 Aug 2022 09:12:01 GMT
Server: Kestrel
https://localhost:7095/Pizza>
若要验证披萨是否已更新,可以使用以下命令重新运行 GET
操作:
get 3
前面的命令将返回新更新的披萨:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Apr 2021 23:27:37 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 3,
"name": "Hawaiian",
"isGlutenFree": false
}
https://localhost:7095/Pizza> get 3
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 09:13:14 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": 3,
"name": "Veggie1",
"isGlutenFree": true
}
https://localhost:7095/Pizza>
如果运行以下命令,我们的 API 还可以通过 DELETE
操作删除新创建的披萨:
delete 3
前面的命令将返回表示成功的 204 No Content
结果:
HTTP/1.1 204 No Content
Date: Fri, 02 Apr 2021 23:30:04 GMT
Server: Kestrel
若要验证披萨是否已删除,可以使用以下命令重新运行 GET
操作:
get
前面的命令将返回原始披萨作为结果:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 02 Apr 2021 23:31:15 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"id": 1,
"name": "Classic Italian",
"isGlutenFree": false
},
{
"id": 2,
"name": "Veggie",
"isGlutenFree": true
}
]
https://localhost:7095/Pizza> delete 4
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8
Date: Thu, 11 Aug 2022 09:14:48 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
"title": "Not Found",
"status": 404,
"traceId": "00-4e168b877c5aad681f7a7e976dda72e7-54d5163bbb02fb9b-00"
}
https://localhost:7095/Pizza> delete 3
HTTP/1.1 204 No Content
Date: Thu, 11 Aug 2022 09:14:56 GMT
Server: Kestrel
https://localhost:7095/Pizza> get
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 11 Aug 2022 09:15:11 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"id": 1,
"name": "Classic Italian",
"isGlutenFree": false
},
{
"id": 2,
"name": "Veggie",
"isGlutenFree": true
}
]
https://localhost:7095/Pizza>
已完成了对使用 ASP.NET Core 新创建的 Web API 的实现和测试。