2021-03-07
本教程介绍使用 ASP.NET Core 构建 Web API 的基础知识。
在本教程中,你将了解:
创建 Web API 项目。
添加模型类和数据库上下文。
使用 CRUD 方法构建控制器。
配置路由、URL 路径和返回值。
使用 Postman 调用 Web API。
在结束时,你会获得可以管理存储在数据库中的“待办事项”的 Web API。
本教程将创建以下 API:
API | 描述 | 请求正文 | 响应正文 |
---|---|---|---|
GET /api/TodoItems | 获取所有待办事项 | None | 待办事项的数组 |
GET /api/TodoItems/{id} | 按 ID 获取项 | None | 待办事项 |
POST /api/TodoItems | 添加新项 | 待办事项 | 待办事项 |
PUT /api/TodoItems/{id} | 更新现有项 | 待办事项 | None |
DELETE /api/TodoItems/{id} | 删除项 | None | None |
下图显示了应用的设计。
右侧的框代表客户端
具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2019 16.8 或更高版本
从“文件”菜单中选择“新建”>“项目” 。
选择“ASP.NET Core Web 应用程序”模板,再单击“下一步” 。
将项目命名为 TodoApi,然后单击“创建”。
在“创建新的 ASP.NET Core Web 应用程序”对话框中,确认选择 .NET Core 和 ASP.NET Core 5.0 。 选择“API”模板,然后单击“创建” 。
项目模板创建了一个支持 Swagger 的 WeatherForecast
API。
按 Ctrl+F5 以在不使用调试程序的情况下运行。
Visual Studio 会显示以下对话框:
此项目配置为使用SSL。
如果信任 IIS Express SSL 证书,请选择“是”。
将显示以下对话框:
安全警告框
如果你同意信任开发证书,请选择“是”。
有关信任 Firefox 浏览器的信息,请参阅 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 证书错误。
Visual Studio 将启动:
IIS Express Web 服务器。
默认浏览器,并导航到 https://localhost:<port>/swagger/index.html
,其中 <port>
是随机选择的端口号。
随即显示 Swagger 页面 /swagger/index.html
。 选择 GET > “试用” > “执行” 。 页面将显示:
用于测试 WeatherForecast API 的 Curl 命令。
用于测试 WeatherForecast API 的 URL。
响应代码、正文和标头。
包含媒体类型、示例值和架构的下拉列表框。
Swagger 用于为 Web API 生成有用的文档和帮助页面。 本教程重点介绍如何创建 Web API。 有关 Swagger 的详细信息,请参阅 带有 Swagger/OpenAPI 的 ASP.NET Core Web API 文档。
将请求 URL 复制粘贴到浏览器中:https://localhost:<port>/WeatherForecast
返回类似于以下项的 JSON:
JSON
[ { "date": "2019-07-16T19:04:05.7257911-06:00", "temperatureC": 52, "temperatureF": 125, "summary": "Mild" }, { "date": "2019-07-17T19:04:05.7258461-06:00", "temperatureC": 36, "temperatureF": 96, "summary": "Warm" }, { "date": "2019-07-18T19:04:05.7258467-06:00", "temperatureC": 39, "temperatureF": 102, "summary": "Cool" }, { "date": "2019-07-19T19:04:05.7258471-06:00", "temperatureC": 10, "temperatureF": 49, "summary": "Bracing" }, { "date": "2019-07-20T19:04:05.7258474-06:00", "temperatureC": -1, "temperatureF": 31, "summary": "Chilly" } ]
在 Properties\launchSettings.json 中,将 launchUrl
从 "swagger"
更新为 "api/TodoItems"
:
JSON
"launchUrl": "api/TodoItems",
由于已删除 Swagger,上述标记会将启动的 URL 更改为添加到以下部分的控制器的 GET 方法。
模型是一组表示应用管理的数据的类。 此应用的模型是单个 TodoItem
类。
在“解决方案资源管理器”中,右键单击项目。 选择“添加” > “新建文件夹”。 将文件夹命名为 Models。
右键单击 Models 文件夹,然后选择“添加” > “类” 。 将类命名为 TodoItem,然后选择“添加”。
将模板代码替换为以下内容:
C#
namespace TodoApi.Models{ public class TodoItem { public long Id { get; set; } public string Name { get; set; } public bool IsComplete { get; set; } } }
Id
属性用作关系数据库中的唯一键。
模型类可位于项目的任意位置,但按照惯例会使用 Models 文件夹。
数据库上下文是为数据模型协调 Entity Framework 功能的主类。 此类由 Microsoft.EntityFrameworkCore.DbContext 类派生而来。
在“工具”菜单中,依次选择“NuGet 包管理器”、“管理解决方案的 NuGet 包” 。
选择“浏览”选项卡,然后在搜索框中输入“Microsoft.EntityFrameworkCore.InMemory” 。
在左窗格中选择“Microsoft.EntityFrameworkCore.InMemory”。
选中右窗格中的“项目”复选框,然后选择“安装” 。
右键单击 Models 文件夹,然后选择“添加” > “类” 。 将类命名为 TodoContext,然后单击“添加”。
输入以下代码:
C#
using Microsoft.EntityFrameworkCore;namespace TodoApi.Models{ public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } } }
在 ASP.NET Core 中,服务(如数据库上下文)必须向依赖关系注入 (DI) 容器进行注册。 该容器向控制器提供服务。
使用以下代码更新 Startup.cs:
C#
// Unused usings removedusing Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Hosting;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.EntityFrameworkCore;using TodoApi.Models;namespace TodoApi{ public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) {services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList")); services.AddControllers(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
前面的代码:
删除 Swagger 调用。
删除未使用的 using
声明。
将数据库上下文添加到 DI 容器。
指定数据库上下文将使用内存中数据库。
右键单击 Controllers 文件夹。
选择“添加”>“新建构建项” 。
选择“其操作使用实体框架的 API 控制器”,然后选择“添加” 。
在“添加其操作使用实体框架的 API 控制器”对话框中:
在“模型类”中选择“TodoItem (TodoApi.Models)” 。
在“数据上下文类”中选择“TodoContext (TodoApi.Models)” 。
选择“添加”。
生成的代码:
使用 [ApiController]
属性标记类。 此属性指示控制器响应 Web API 请求。 有关该属性启用的特定行为的信息,请参阅 使用 ASP.NET Core 创建 Web API。
使用 DI 将数据库上下文 (TodoContext
) 注入到控制器中。 数据库上下文将在控制器中的每个 CRUD 方法中使用。
ASP.NET Core 模板:
具有视图的控制器在路由模板中包含 [action]
。
API 控制器不在路由模板中包含 [action]
。
如果 [action]
标记不在路由模板中,则从路由中排除操作名称。 也就是说,不会在匹配的路由中使用操作的关联方法名称。
更新 PostTodoItem
中的 return 语句,以使用 nameof 运算符:
C#
// POST: api/TodoItems[HttpPost]public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem) { _context.TodoItems.Add(todoItem); await _context.SaveChangesAsync(); //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem); return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem); }
前面的代码是 HTTP POST 方法,如 [HttpPost]
属性所指示。 该方法从 HTTP 请求正文获取待办事项的值。
有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
CreatedAtAction 方法:
如果成功,将返回 HTTP 201 状态代码。 HTTP 201 是在服务器上创建新资源的 HTTP POST 方法的标准响应。
向响应添加位置标头。 Location
标头指定新建的待办事项的 URI。 有关详细信息,请参阅创建的 10.2.2 201。
引用 GetTodoItem
操作以创建 Location
标头的 URI。 C# nameof
关键字用于避免在 CreatedAtAction
调用中硬编码操作名称。
本教程使用 Postman 测试 Web API。
安装 Postman
启动 Web 应用。
启动 Postman。
禁用 SSL 证书验证
在“文件”>“设置”(“常规” 选项卡)中,禁用“SSL 证书验证”。
警告
在测试控制器之后重新启用 SSL 证书验证。
创建新请求。
将 HTTP 方法设置为 POST
。
将 URI 设置为 https://localhost:<port>/api/TodoItems
。 例如 https://localhost:5001/api/TodoItems
。
选择“正文”选项卡。
选择“原始”单选按钮。
将类型设置为 JSON (application/json)
在请求正文中,输入待办事项的 JSON:
JSON
{ "name":"walk dog", "isComplete":true}
选择 Send。
你可以在浏览器中测试位置标头 URI。 将位置标头 URI 复制粘贴到浏览器中。
若要在 Postman 中测试,请执行以下操作:
在 Headers 窗格中选择 Response 选项卡。
复制 Location 标头值:
将 HTTP 方法设置为 GET
。
将 URI 设置为 https://localhost:<port>/api/TodoItems/1
。 例如 https://localhost:5001/api/TodoItems/1
。
选择 Send。
实现了两个 GET 终结点:
GET /api/TodoItems
GET /api/TodoItems/{id}
通过从浏览器或 Postman 调用两个终结点来测试应用。 例如:
https://localhost:5001/api/TodoItems
https://localhost:5001/api/TodoItems/1
对 GetTodoItems
的调用生成类似于以下项的响应:
JSON
[ { "id": 1, "name": "Item1", "isComplete": false } ]
创建新请求。
将 HTTP 方法设置为“GET”。
将请求 URI 设置为 https://localhost:<port>/api/TodoItems
。 例如 https://localhost:5001/api/TodoItems
。
在 Postman 中设置 Two pane view 。
选择 Send。
此应用使用内存中数据库。 如果应用已停止并启动,则前面的 GET 请求将不会返回任何数据。 如果未返回任何数据,将数据 POST 到应用。
[HttpGet]
属性表示响应 HTTP GET 请求的方法。 每个方法的 URL 路径构造如下所示:
在控制器的 Route
属性中以模板字符串开头:
C#
[Route("api/[controller]")][ApiController]public class TodoItemsController : ControllerBase{ private readonly TodoContext _context; public TodoItemsController(TodoContext context) { _context = context; }
将 [controller]
替换为控制器的名称,按照惯例,在控制器类名称中去掉“Controller”后缀。 对于此示例,控制器类名称为“TodoItems”控制器,因此控制器名称为“TodoItems”。 ASP.NET Core 路由不区分大小写。
如果 [HttpGet]
属性具有路由模板(例如 [HttpGet("products")]
),则将它追加到路径。 此示例不使用模板。 有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
在下面的 GetTodoItem
方法中,"{id}"
是待办事项的唯一标识符的占位符变量。 调用 GetTodoItem
时,URL 中 "{id}"
的值会在 id
参数中提供给方法。
C#
// GET: api/TodoItems/5[HttpGet("{id}")]public async Task<ActionResult<TodoItem>> GetTodoItem(long id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } return todoItem; }
GetTodoItems
和 GetTodoItem
方法的返回类型是 ActionResult<T> 类型。 ASP.NET Core 自动将对象序列化为 JSON,并将 JSON 写入响应消息的正文中。 此返回类型的响应代码为 200 OK(假设没有未处理的异常)。 未经处理的异常将转换为 5xx 错误。
ActionResult
返回类型可以表示大范围的 HTTP 状态代码。 例如,GetTodoItem
可以返回两个不同的状态值:
检查 PutTodoItem
方法:
C#
// PUT: api/TodoItems/5[HttpPut("{id}")]public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem){ if (id != todoItem.Id) { return BadRequest(); } _context.Entry(todoItem).State = EntityState.Modified; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!TodoItemExists(id)) { return NotFound(); } else { throw; } } return NoContent(); }
PutTodoItem
与 PostTodoItem
类似,但是使用的是 HTTP PUT。 响应是 204(无内容)。 根据 HTTP 规范,PUT 请求需要客户端发送整个更新的实体,而不仅仅是更改。 若要支持部分更新,请使用 HTTP PATCH。
如果在调用 PutTodoItem
时出错,请调用 GET
以确保数据库中有项目。
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。 在进行 PUT 调用之前,数据库中必须有一个项。 调用 GET,以确保在调用 PUT 之前数据库中存在项。
更新 ID 为 1 的待办事项并将其名称设置为 "feed fish"
:
JSON
{ "Id":1, "name":"feed fish", "isComplete":true }
下图显示 Postman 更新:
检查 DeleteTodoItem
方法:
C#
// DELETE: api/TodoItems/5[HttpDelete("{id}")]public async Task<IActionResult> DeleteTodoItem(long id){ var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } _context.TodoItems.Remove(todoItem); await _context.SaveChangesAsync(); return NoContent(); }
使用 Postman 删除待办事项:
将方法设置为 DELETE
。
设置要删除的对象的 URI,例如 https://localhost:5001/api/TodoItems/1
。
选择 Send。
目前,示例应用公开了整个 TodoItem
对象。 生产应用通常使用模型的子集来限制输入和返回的数据。 这背后有多种原因,但安全性是主要原因。 模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。 本文使用的是 DTO。
DTO 可用于:
防止过度发布。
隐藏客户端不应查看的属性。
省略一些属性以缩减有效负载大小。
平展包含嵌套对象的对象图。 对客户端而言,平展的对象图可能更方便。
若要演示 DTO 方法,请更新 TodoItem
类,使其包含机密字段:
C#
namespace TodoApi.Models{ public class TodoItem { public long Id { get; set; } public string Name { get; set; } public bool IsComplete { get; set; }public string Secret { get; set; } } }
此应用需要隐藏机密字段,但管理应用可以选择公开它。
确保可以发布和获取机密字段。
创建 DTO 模型:
C#
public class TodoItemDTO{ public long Id { get; set; } public string Name { get; set; } public bool IsComplete { get; set; } }
更新 TodoItemsController
以使用 TodoItemDTO
:
C#
// GET: api/TodoItems[HttpGet]public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems() { return await _context.TodoItems .Select(x => ItemToDTO(x)) .ToListAsync(); } [HttpGet("{id}")]public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } return ItemToDTO(todoItem); } [HttpPut("{id}")]public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO){ if (id != todoItemDTO.Id) { return BadRequest(); } var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } todoItem.Name = todoItemDTO.Name; todoItem.IsComplete = todoItemDTO.IsComplete; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) when (!TodoItemExists(id)) { return NotFound(); } return NoContent(); } [HttpPost]public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO) { var todoItem = new TodoItem { IsComplete = todoItemDTO.IsComplete, Name = todoItemDTO.Name }; _context.TodoItems.Add(todoItem); await _context.SaveChangesAsync(); return CreatedAtAction( nameof(GetTodoItem), new { id = todoItem.Id }, ItemToDTO(todoItem)); } [HttpDelete("{id}")]public async Task<IActionResult> DeleteTodoItem(long id){ var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } _context.TodoItems.Remove(todoItem); await _context.SaveChangesAsync(); return NoContent(); }private bool TodoItemExists(long id) => _context.TodoItems.Any(e => e.Id == id);private static TodoItemDTO ItemToDTO(TodoItem todoItem) => new TodoItemDTO { Id = todoItem.Id, Name = todoItem.Name, IsComplete = todoItem.IsComplete };
确保无法发布或获取机密字段。
请参阅教程:使用 JavaScript 调用 ASP.NET Core Web API。
ASP.NET Core Identity 将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。 若要保护 Web API 和 SPA,请使用以下项之一:
IdentityServer4 是适用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。 IdentityServer4 支持以下安全功能:
身份验证即服务 (AaaS)
跨多个应用程序类型的单一登录/注销 (SSO)
API 的访问控制
Federation Gateway
有关详细信息,请参阅欢迎使用 IdentityServer4。
查看或下载本教程的示例代码。 请参阅如何下载。
有关更多信息,请参见以下资源: