本教程介绍使用 ASP.NET Core 构建 Web API 的基础知识。
在本教程中,你将了解:
- 创建 Web API 项目。
- 添加模型类和数据库上下文。
- 添加控制器。
- 添加 CRUD 方法。
- 配置路由和 URL 路径。
- 指定返回值。
- 使用 Postman 调用 Web API。
- 使用 JavaScript 调用 Web API。
在结束时,你会获得可以管理存储在关系数据库中的“待办事项”的 Web API。
概述
本教程将创建以下 API:
API | 说明 | 请求正文 | 响应正文 |
---|---|---|---|
GET /api/TodoItems | 获取所有待办事项 | 无 | 待办事项的数组 |
GET /api/TodoItems/{id} | 按 ID 获取项 | 无 | 待办事项 |
POST /api/TodoItems | 添加新项 | 待办事项 | 待办事项 |
PUT /api/TodoItems/{id} | 更新现有项 | 待办事项 | 无 |
DELETE /api/TodoItems/{id} | 删除项 | 无 | 无 |
下图显示了应用的设计。
系统必备
- Visual Studio 2019 与 ASP.NET 和 Web 开发 工作负载
- .NET Core SDK 2.2 或更高版本
警告
如果使用 Visual Studio 2017,请参阅 dotnet/sdk 问题 #3124,以了解无法与 Visual Studio 一起使用的 .NET Core SDK 版本的信息。
创建 Web 项目
- 从“文件”菜单中选择“新建”>“项目” 。
- 选择“ASP.NET Core Web 应用程序”模板,再单击“下一步” 。
- 将项目命名为 TodoApi,然后单击“创建” 。
- 在“创建新的 ASP.NET Core Web 应用程序”对话框中,确认选择“.NET Core”和“ASP.NET Core 2.2” 。 选择“API”模板,然后单击“创建” 。 请不要选择“启用 Docker 支持” 。
测试 API
项目模板会创建 values
API。 从浏览器调用 Get
方法以测试应用。
按 Ctrl+F5 运行应用。 Visual Studio 启动浏览器并导航到 https://localhost:<port>/api/values
,其中 <port>
是随机选择的端口号。
如果出现询问是否应信任 IIS Express 证书的对话框,则选择“是” 。 在接下来出现的“安全警告” 对话框中,选择“是” 。
会返回以下 JSON:
JSON复制
["value1","value2"]
添加模型类
模型 是一组表示应用管理的数据的类。 此应用的模型是单个 TodoItem
类。
- Visual Studio
-
在“解决方案资源管理器” 中,右键单击项目。 选择“添加” > “新建文件夹” 。 将文件夹命名为“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
类派生而来。
- Visual Studio
- 右键单击“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 :
// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;
namespace TodoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the
//container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP
//request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for
// production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
前面的代码:
- 删除未使用的
using
声明。 - 将数据库上下文添加到 DI 容器。
- 指定数据库上下文将使用内存中数据库。
添加控制器
-
右键单击 Controllers 文件夹。
-
选择“添加”>“新项” 。
-
在“添加新项”对话框中,选择“API 控制器类”模板 。
-
将类命名为 TodoController,然后选择“添加” 。
-
将模板代码替换为以下代码:
C#复制
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using TodoApi.Models; namespace TodoApi.Controllers { [Route("api/[controller]")] [ApiController] public class TodoController : ControllerBase { private readonly TodoContext _context; public TodoController(TodoContext context) { _context = context; if (_context.TodoItems.Count() == 0) { // Create a new TodoItem if collection is empty, // which means you can't delete all TodoItems. _context.TodoItems.Add(new TodoItem { Name = "Item1" }); _context.SaveChanges(); } } } }
前面的代码:
- 定义了没有方法的 API 控制器类。
- 使用
[ApiController]
属性标记类。 此属性指示控制器响应 Web API 请求。 有关该属性启用的特定行为的信息,请参阅 使用 ASP.NET Core 创建 Web API。 - 使用 DI 将数据库上下文 (
TodoContext
) 注入到控制器中。 数据库上下文将在控制器中的每个 CRUD 方法中使用。 - 如果数据库为空,则将名为
Item1
的项添加到数据库。 此代码位于构造函数中,因此在每次出现新 HTTP 请求时运行。 如果删除所有项,则构造函数会在下次调用 API 方法时再次创建Item1
。 因此删除可能看上去不起作用,不过实际上确实有效。
添加 Get 方法
若要提供检索待办事项的 API,请将以下方法添加到 TodoController
类中:
C#复制
// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
// GET: api/Todo/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;
}
这些方法实现两个 GET 终结点:
GET /api/todo
GET /api/todo/{id}
如果应用仍在运行,请停止它。 然后再次运行它以包括最新更改。
通过从浏览器调用两个终结点来测试应用。 例如:
https://localhost:<port>/api/todo
https://localhost:<port>/api/todo/1
以下 HTTP 响应通过调用 GetTodoItems
来生成:
JSON复制
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
路由和 URL 路径
[HttpGet]
属性表示响应 HTTP GET 请求的方法。 每个方法的 URL 路径构造如下所示:
-
在控制器的
Route
属性中以模板字符串开头:namespace TodoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
private readonly TodoContext _context; -
}
-
将
[controller]
替换为控制器的名称,按照惯例,在控制器类名称中去掉“Controller”后缀。 对于此示例,控制器类名称为“Todo”控制器,因此控制器名称为“todo” 。 ASP.NET Core 路由不区分大小写。 -
如果
[HttpGet]
属性具有路由模板(例如[HttpGet("products")]
),则将它追加到路径。 此示例不使用模板。 有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
在下面的 GetTodoItem
方法中,"{id}"
是待办事项的唯一标识符的占位符变量。调用 GetTodoItem
时,URL 中 "{id}"
的值会在 id
参数中提供给方法。
C#复制
// GET: api/Todo/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。 未经处理的异常将转换为 5xx 错误。
ActionResult
返回类型可以表示大范围的 HTTP 状态代码。 例如,GetTodoItem
可以返回两个不同的状态值:
- 如果没有任何项与请求的 ID 匹配,则该方法将返回 404 NotFound 错误代码。
- 否则,此方法将返回具有 JSON 响应正文的 200。 返回
item
则产生 HTTP 200 响应。
测试 GetTodoItems 方法
本教程使用 Postman 测试 Web API。
- 安装 Postman。
- 启动 Web 应用。
- 启动 Postman。
- 禁用 SSL 证书验证。
- 在“文件”>“设置”(“常规”选项卡)中,禁用“SSL 证书验证” 。
警告
在测试控制器之后重新启用 SSL 证书验证。
- 创建新请求。
- 将 HTTP 方法设置为“GET” 。
- 将请求 URL 设置为
https://localhost:<port>/api/todo
。 例如https://localhost:5001/api/todo
。
- 在 Postman 中设置Two pane view 。
- 选择Send。
添加创建方法
在 Controllers / TodoController.cs 中添加以下 PostTodoItem
方法 :
C#复制
// POST: api/Todo
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
_context.TodoItems.Add(item);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
}
前面的代码是 HTTP POST 方法,如 [HttpPost]
属性所指示。 该方法从 HTTP 请求正文获取待办事项的值。
CreatedAtAction
方法:
-
如果成功,则返回 HTTP 201 状态代码。 HTTP 201 是在服务器上创建新资源的 HTTP POST 方法的标准响应。
-
将
Location
标头添加到响应。Location
标头指定新建的待办事项的 URI。有关详细信息,请参阅创建的 10.2.2 201。 -
引用
GetTodoItem
操作以创建Location
标头的 URI。 C#nameof
关键字用于避免在CreatedAtAction
调用中硬编码操作名称。C#复制
// GET: api/Todo/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; }
测试 PostTodoItem 方法
-
生成项目。
-
在 Postman 中,将 HTTP 方法设置为
POST
。 -
选择“正文”选项卡 。
-
选择“原始”单选按钮 。
-
将类型设置为 JSON (application/json)
-
在请求正文中,输入待办事项的 JSON:
JSON复制
{ "name":"walk dog", "isComplete":true }
-
选择Send。
如果收到 405 不允许的方法错误,则可能是由于未在添加
PostTodoItem
方法之后编译项目。
测试位置标头 URI
-
在Headers 窗格中选择Response 选项卡。
-
复制Location 标头值:
-
将方法设置为“GET”。
-
粘贴 URI(例如,
https://localhost:5001/api/Todo/2
)。 -
选择Send。
添加 PutTodoItem 方法
添加以下 PutTodoItem
方法:
C#复制
// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
if (id != item.Id)
{
return BadRequest();
}
_context.Entry(item).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
PutTodoItem
与 PostTodoItem
类似,但是使用的是 HTTP PUT。 响应是 204(无内容)。 根据 HTTP 规范,PUT 请求需要客户端发送整个更新的实体,而不仅仅是更改。 若要支持部分更新,请使用 HTTP PATCH。
如果在调用 PutTodoItem
时出错,请调用 GET
以确保数据库中有项目。
测试 PutTodoItem 方法
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。 在进行 PUT 调用之前,数据库中必须有一个项。 调用 GET,以确保在调用 PUT 之前数据库中存在项目。
更新 id = 1 的待办事项并将其名称设置为“feed fish”:
JSON复制
{
"ID":1,
"name":"feed fish",
"isComplete":true
}
下图显示 Postman 更新:
添加 DeleteTodoItem 方法
添加以下 DeleteTodoItem
方法:
C#复制
// DELETE: api/Todo/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();
}
DeleteTodoItem
响应是 204(无内容)。
测试 DeleteTodoItem 方法
使用 Postman 删除待办事项:
- 将方法设置为
DELETE
。 - 设置要删除的对象的 URI,例如
https://localhost:5001/api/todo/1
。 - 选择Send。
可通过示例应用删除所有项。 但如果删除最后一项,则在下次调用 API 时,模型类构造函数会创建一个新项。