开发 ASP.NET Core 5.0 API

本文最后更新于 2021年4月4日 晚上

根据官方示例开发一个简单的 TODO API.

API 需求概述

要实现的是一个 TODO LIST 的后端, 需要实现如下 API:

  1. GET /api/TodoItems: 获取所有 TODO 列表
  2. GET /api/TodoItems/{id}: 获取指定 ID 的待办项
  3. POST /api/TodoItems: 添加一个待办事项
  4. PUT /api/TodoItems/{id}: 修改待办事项
  5. DELETE /api/TodoItems/{id}: 删除一个待办事项

且工程的结构按如下方式组织:

开始开发

根据结构设计, 按如下步骤实现整个项目:

  1. 实现数据访问层: 创建数据模型, 添加和使用数据库上下文.
  2. 实现表现层: 构建控制器(实现 API 端点), 创建 DTO 用于对外传递数据.

下面逐步进行实现.

实现数据访问层

数据访问层由如下两大内容构成:

  1. 模型类: 数据模型, 用于表示 APP 内部的数据.
  2. 数据库上下文: 用于访问数据库.

数据模型创建

本示例比较简单, 只有一个数据模型 TodoItem, 用于表示单个待办事项:

1
2
3
4
5
6
7
8
9
10
namespace TodoApi.Models
{
public class TodoItem
{
/// Id 作为关系数据库的唯一键, 即 Key.
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}

数据库上下文实现及数据库连接

数据库上下文类用于和 Entity Framework 协同工作, 派生自 Microsoft.EntityFrameworkCore.DbContext.

在开始开发阶段,可以使用内存数据库来测试,这样可以提高开发速度。

本示例准备使用 SQL Server 作为数据库, 故需要引入 Microsoft.EntityFrameworkCore.SqlServer 包, 且由于使用 ASP.NET Core 5.0, 所以需要对应 5.0 以上版本的依赖.

创建上下文 TodoContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; }
}
}

要使用数据库上下文, 需要进行依赖注入(DI), 在 Startup 的 ConfigureServices 方法中注入:

1
2
3
4
5
6
public void ConfigureServices(IServiceCollection services)
{
// 使用内存数据库进行测试, 可以自己配置 SQL Server 数据库
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddControllers();
}

表现层开发

表现层用于对用户提供服务, 在这里需要实现一个提供 API 端点的控制器.

Todo 控制器实现

创建一个名为 TodoController 的控制器, 并用 [ApiController] 注解, 它派生自 ControllerBase:

1
2
3
4
5
6
7
8
9
10
11
12
[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;

// 构造方法注入数据库上下文
public TodoItemsController(TodoContext context)
{
_context = context;
}
}

在其中主要实现四个方法, 即: 增删改查.

获取所有待办事项列表:

1
2
3
4
5
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}

获取指定 ID 的待办事项:

1
2
3
4
5
6
7
8
9
10
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}

更新单个待办事项:

1
2
3
4
5
6
7
8
9
10
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
// 保证不会修改 item 的 Id
if (id != todoItem.Id) return BadRequest();
// 使用 Entry 接口来更新数据
_context.Entry(todoItem).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}

创建待办事项:

1
2
3
4
5
6
7
8
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
// 不直接返回对象的好处有很多,也算作一种原则。
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

删除待办事项:

1
2
3
4
5
6
7
8
9
10
// 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 检查接口

通过 Postman 检查接口是否正常即可.

防止过度发布: 使用 DTO

目前上述内容中对外公布的是完整的 TodoItem, 但生产应用通常使用数据模型的子集来限制输入和返回数据, 主要原因是安全性, 另外还有诸多原因. 模型的子集也叫 DTO/输入模型或视图模型, 即数据传输对象, 顾名思义, 就是用于在不同物理层间传递数据的对象.

使用 DTO 可以:

  • 防止过度发布
  • 隐藏客户端不该看到的数据属性
  • 省略一些属性以减少负载大小
  • 方便客户端使用

针对上面的例子, 比如在 TodoItem 中包含一个 Secret 属性, 但不想对客户端展示, 则可以创建一个 DTO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
public string Secret { get; set; }
}

// ...

public class TodoItemDTO
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}

创建 DTO 后, 就可以在表现层将数据模型替换为 DTO, 比如可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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);
}

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};

这样客户端得到的就是 DTO 了.


开发 ASP.NET Core 5.0 API
https://blog.rayy.top/2020/11/14/2020-11-14-apitutorial/
作者
貘鸣
发布于
2020年11月14日
许可协议