- 教程:使用 ASP.NET Core 创建 Web APITutorial: Create a web API with ASP.NET Core
- 概述Overview
- 先决条件Prerequisites
- 创建 Web 项目Create a web project
- 添加模型类Add a model class
- 添加数据库上下文Add a database context
- 添加 TodoContext 数据库上下文Add the TodoContext database context
- 注册数据库上下文Register the database context
- 构建控制器Scaffold a controller
- 检查 PostTodoItem create 方法Examine the PostTodoItem create method
- 检查 GET 方法Examine the GET methods
- 路由和 URL 路径Routing and URL paths
- 返回值Return values
- PutTodoItem 方法The PutTodoItem method
- DeleteTodoItem 方法The DeleteTodoItem method
- 防止过度发布Prevent over-posting
- 使用 JavaScript 调用 Web APICall the web API with JavaScript
- 概述Overview
- 先决条件Prerequisites
- 创建 Web 项目Create a web project
- 添加模型类Add a model class
- 添加数据库上下文Add a database context
- 注册数据库上下文Register the database context
- 添加控制器Add a controller
- 添加 Get 方法Add Get methods
- 路由和 URL 路径Routing and URL paths
- 返回值Return values
- 测试 GetTodoItems 方法Test the GetTodoItems method
- 添加创建方法Add a Create method
- 添加 PutTodoItem 方法Add a PutTodoItem method
- 添加 DeleteTodoItem 方法Add a DeleteTodoItem method
- 使用 JavaScript 调用 Web APICall the web API with JavaScript
- 向 Web API 添加身份验证支持Add authentication support to a web API
- 其他资源Additional resources
教程:使用 ASP.NET Core 创建 Web APITutorial: Create a web API with ASP.NET Core
本文内容
作者:Rick Anderson、Kirk Larkin 和 Mike Wasson
本教程介绍使用 ASP.NET Core 构建 Web API 的基础知识。
在本教程中,你将了解:
- 创建 Web API 项目。
- 添加模型类和数据库上下文。
- 使用 CRUD 方法构建控制器。
- 配置路由、URL 路径和返回值。
- 使用 Postman 调用 Web API。
在结束时,你会获得可以管理存储在数据库中的“待办事项”的 Web API。
概述Overview
本教程将创建以下 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 |
下图显示了应用的设计。
先决条件Prerequisites
- 具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2019 16.4 或更高版本
- .NET Core 3.1 SDK 或更高版本
Visual Studio Code 说明使用用于 ASP.NET Core 的 .NET Core CLI 开发功能,如项目创建。可在任何平台(macOS、Linux 或 Windows)上或在任何代码编辑器中遵循这些说明。如果使用 Visual Studio Code 以外的其他内容,则可能需要进行少量更改。
创建 Web 项目Create a web project
- 从“文件”菜单中选择“新建”>“项目” 。
- 选择“ASP.NET Core Web 应用程序”模板,再单击“下一步” 。
- 将项目命名为 TodoApi,然后单击“创建” 。
- 在“创建新的 ASP.NET Core Web 应用程序”对话框中,确认选择“.NET Core”和“ASP.NET Core 3.1” 。选择“API”模板,然后单击“创建” 。
打开集成终端。
将目录 (
cd
) 更改为包含项目文件夹的文件夹。运行以下命令:
dotnet new webapi -o TodoApi
cd TodoApi
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.InMemory
code -r ../TodoApi
- 当对话框询问是否要将所需资产添加到项目时,选择“是” 。
前面的命令:
- 创建新的 web API 项目,并在 Visual Studio Code 中打开它。
- 添加下一部分中所需的 NuGet 包。
- 选择“文件”>“新建解决方案” 。
- 选择“.NET Core” >“应用” >“API” >“下一步”。
在“配置新的 ASP.NET Core Web API”对话框中,将目标框架选择为*“.NET Core 3.1” 。
输入“TodoApi” 作为“项目名称” ,然后选择“创建” 。
访问 Visual studio for Mac 上的命令终端Accessing a Command Terminal on Visual Studios for Mac
首次访问 Mac 上的命令终端需要以下设置配置:
- 导航到“系统首选项”>“键盘”>“快捷方式”>“服务”。
- 在“文件和文件夹”下,验证是否选中了“文件夹中的新终端”。
上述说明以两种方式实现访问命令终端:从 Visual Studio 内访问或通过查找器访问。
若要从 Visual Studio for Mac 访问命令终端,请执行以下操作:To access a command terminal from Visual Studio for Mac:
- 右键单击项目名称。
- 导航到“工具”>“在终端中打开”。
若要通过查找器访问命令终端,请执行以下操作:To access a command terminal from Finder:
- 右键单击所需文件夹。
- 在列表底部,选择“文件夹中的新终端”。
在项目文件夹中打开命令终端并运行以下命令:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.InMemory
测试 APITest the API
项目模板会创建 WeatherForecast
API。从浏览器调用 Get
方法以测试应用。
按 Ctrl+F5 运行应用。Visual Studio 启动浏览器并导航到 https://localhost:<port>/WeatherForecast
,其中 <port>
是随机选择的端口号。
如果出现询问是否应信任 IIS Express 证书的对话框,则选择“是” 。在接下来出现的“安全警告” 对话框中,选择“是” 。
按 Ctrl+F5 运行应用。在浏览器中,转到以下 URL:https://localhost:5001/WeatherForecast。
选择“运行” > “开始调试” 以启动应用。Visual Studio for Mac 会启动浏览器并导航到 https://localhost:<port>
,其中 <port>
是随机选择的端口号。将返回 HTTP 404(未找到)错误。将 /WeatherForecast
追加到 URL(将 URL 更改为 https://localhost:<port>/WeatherForecast
)。
返回类似于以下项的 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"
}
]
添加模型类Add a model class
模型 是一组表示应用管理的数据的类。此应用的模型是单个 TodoItem
类。
在“解决方案资源管理器” 中,右键单击项目。选择“添加” > “新建文件夹” 。将文件夹命名为“Models” 。
右键单击“Models” 文件夹,然后选择“添加” > “类” 。将类命名为 TodoItem,然后选择“添加” 。
将模板代码替换为以下代码:
添加名为“Models”的文件夹 。
使用以下代码将
TodoItem
类添加到 Models 文件夹:
- 右键单击项目。选择“添加” > “新建文件夹” 。将文件夹命名为“Models” 。
右键单击“Models” 文件夹,然后选择“添加” >“新建文件” >“常规” >“空类” 。
将类命名为“TodoItem”,然后单击“新建” 。
将模板代码替换为以下代码:
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
Id
属性用作关系数据库中的唯一键。
模型类可位于项目的任意位置,但按照惯例会使用 Models 文件夹。
添加数据库上下文Add a database context
数据库上下文是为数据模型协调 Entity Framework 功能的主类 。此类由 Microsoft.EntityFrameworkCore.DbContext
类派生而来。
添加 Add Microsoft.EntityFrameworkCore.SqlServerAdd Microsoft.EntityFrameworkCore.SqlServer
- 在“工具”菜单中,依次选择“NuGet 包管理器”、“管理解决方案的 NuGet 包” 。
- 选择“浏览”选项卡,然后在搜索框中输入 Microsoft.EntityFrameworkCore.SqlServer 。
- 在左窗格中选择“Microsoft.EntityFrameworkCore.SqlServer” 。
- 选中右窗格中的“项目”复选框,然后选择“安装” 。
- 使用上述说明添加
Microsoft.EntityFrameworkCore.InMemory
NuGet 包。
添加 TodoContext 数据库上下文Add the TodoContext database context
- 右键单击“Models” 文件夹,然后选择“添加” > “类” 。将类命名为 TodoContext,然后单击“添加” 。
- 将
TodoContext
类添加到“Models” 文件夹。
- 输入以下代码:
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
}
注册数据库上下文Register the database context
在 ASP.NET Core 中,服务(如数据库上下文)必须向依赖关系注入 (DI) 容器进行注册。该容器向控制器提供服务。
使用以下突出显示的代码更新 Startup.cs :
// Unused usings removed
using 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();
});
}
}
}
前面的代码:
- 删除未使用的
using
声明。 - 将数据库上下文添加到 DI 容器。
- 指定数据库上下文将使用内存中数据库。
构建控制器Scaffold a controller
右键单击 Controllers 文件夹。
选择“添加”>“新建构建项” 。
选择“其操作使用实体框架的 API 控制器”,然后选择“添加” 。
在“添加其操作使用实体框架的 API 控制器”对话框中 :
- 在“模型类”中选择“TodoItem (TodoApi.Models)” 。
- 在“数据上下文类”中选择“TodoContext (TodoAPI.Models)” 。
- 选择“添加” 。
运行以下命令:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator controller -name TodoItemsController -async -api -m TodoItem -dc TodoContext -outDir Controllers
前面的命令:
- 添加构建所需的 NuGet 包。
- 安装构建引擎 (
dotnet-aspnet-codegenerator
)。 - 构建
TodoItemsController
。
生成的代码:
- 使用
[ApiController]
属性标记类。此属性指示控制器响应 Web API 请求。有关该属性启用的特定行为的信息,请参阅 使用 ASP.NET Core 创建 Web API。 - 使用 DI 将数据库上下文 (
TodoContext
) 注入到控制器中。数据库上下文将在控制器中的每个 CRUD 方法中使用。
ASP.NET Core 模板:
- 具有视图的控制器在路由模板中包含
[action]
。 - API 控制器不在路由模板中包含
[action]
。
如果 [action]
标记不在路由模板中,则从路由中排除操作名称。也就是说,不会在匹配的路由中使用操作的关联方法名称。
检查 PostTodoItem create 方法Examine the PostTodoItem create method
替换 PostTodoItem
中的返回语句,以使用 nameof 运算符:
// 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 请求正文获取待办事项的值。
CreatedAtAction 方法:
- 如果成功,则返回 HTTP 201 状态代码。HTTP 201 是在服务器上创建新资源的 HTTP POST 方法的标准响应。
- 向响应添加位置标头。
Location
标头指定新建的待办事项的 URI。有关详细信息,请参阅创建的 10.2.2 201。 - 引用
GetTodoItem
操作以创建Location
标头的 URI。C#nameof
关键字用于避免在CreatedAtAction
调用中硬编码操作名称。
安装 PostmanInstall Postman
本教程使用 Postman 测试 Web API。
- 安装 Postman
- 启动 Web 应用。
- 启动 Postman。
- 禁用 SSL 证书验证
- 在“文件” >“设置”(“常规” 选项卡)中,禁用“SSL 证书验证”。
警告
在测试控制器之后重新启用 SSL 证书验证。
通过 Postman 测试 PostTodoItemTest PostTodoItem with Postman
创建新请求。
将 HTTP 方法设置为
POST
。选择“正文”选项卡 。
选择“原始”单选按钮 。
将类型设置为 JSON (application/json)
在请求正文中,输入待办事项的 JSON:
{
"name":"walk dog",
"isComplete":true
}
- 选择Send。
测试位置标头 URITest the location header URI
在Headers 窗格中选择Response 选项卡。
复制Location 标头值:
将方法设置为“GET”。
粘贴 URI(例如,
https://localhost:5001/api/TodoItems/1
)。选择Send。
检查 GET 方法Examine the GET methods
这些方法实现两个 GET 终结点:
GET /api/TodoItems
GET /api/TodoItems/{id}
通过从浏览器或 Postman 调用两个终结点来测试应用。例如:
对 GetTodoItems
的调用生成类似于以下项的响应:
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
使用 Postman 测试 GetTest Get with Postman
- 创建新请求。
- 将 HTTP 方法设置为“GET” 。
- 将请求 URL 设置为
https://localhost:<port>/api/TodoItems
。例如https://localhost:5001/api/TodoItems
。 - 在 Postman 中设置Two pane view 。
- 选择Send。
此应用使用内存中数据库。如果应用已停止并启动,则前面的 GET 请求将不会返回任何数据。如果未返回任何数据,将数据 POST 到应用。
路由和 URL 路径Routing and URL paths
[HttpGet]
属性表示响应 HTTP GET 请求的方法。每个方法的 URL 路径构造如下所示:
- 在控制器的
Route
属性中以模板字符串开头:
[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
参数中提供给方法。
// 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;
}
返回值Return values
GetTodoItems
和 GetTodoItem
方法的返回类型是 ActionResult<T> 类型。ASP.NET Core 自动将对象序列化为 JSON,并将 JSON 写入响应消息的正文中。在假设没有未经处理的异常的情况下,此返回类型的响应代码为 200。未经处理的异常将转换为 5xx 错误。
ActionResult
返回类型可以表示大范围的 HTTP 状态代码。例如,GetTodoItem
可以返回两个不同的状态值:
- 如果没有任何项与请求的 ID 匹配,则该方法将返回 404 NotFound 错误代码。
- 否则,此方法将返回具有 JSON 响应正文的 200。返回
item
则产生 HTTP 200 响应。
PutTodoItem 方法The PutTodoItem method
检查 PutTodoItem
方法:
// 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
以确保数据库中有项目。
测试 PutTodoItem 方法Test the PutTodoItem method
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。在进行 PUT 调用之前,数据库中必须有一个项。调用 GET,以确保在调用 PUT 之前数据库中存在项目。
更新 ID = 1 的待办事项并将其名称设置为“feed fish”:
{
"ID":1,
"name":"feed fish",
"isComplete":true
}
下图显示 Postman 更新:
DeleteTodoItem 方法The DeleteTodoItem method
检查 DeleteTodoItem
方法:
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return todoItem;
}
测试 DeleteTodoItem 方法Test the DeleteTodoItem method
使用 Postman 删除待办事项:
- 将方法设置为
DELETE
。 - 设置要删除的对象的 URI,例如
https://localhost:5001/api/TodoItems/1
。 - 选择Send。
防止过度发布Prevent over-posting
目前,示例应用公开了整个 TodoItem
对象。生产应用通常使用模型的子集来限制输入和返回的数据。这背后有多种原因,但安全性是主要原因。模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。本文使用的是 DTO。
DTO 可用于:
- 防止过度发布。
- 隐藏客户端不应查看的属性。
- 省略一些属性以缩减有效负载大小。
- 平展包含嵌套对象的对象图。对客户端而言,平展的对象图可能更方便。
若要演示 DTO 方法,请更新 TodoItem
类,使其包含机密字段:
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
public string Secret { get; set; }
}
此应用需要隐藏机密字段,但管理应用可以选择公开它。
确保可以发布和获取机密字段。
创建 DTO 模型:
public class TodoItemDTO
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
更新 TodoItemsController
以使用 TodoItemDTO
:
[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<TodoItem>> 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 调用 Web APICall the web API with JavaScript
请参阅教程:使用 JavaScript 调用 ASP.NET Core Web API。
在本教程中,你将了解:
- 创建 Web API 项目。
- 添加模型类和数据库上下文。
- 添加控制器。
- 添加 CRUD 方法。
- 配置路由和 URL 路径。
- 指定返回值。
- 使用 Postman 调用 Web API。
- 使用 JavaScript 调用 Web API。
在结束时,你会获得可以管理存储在关系数据库中的“待办事项”的 Web API。
概述Overview
本教程将创建以下 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 |
下图显示了应用的设计。
先决条件Prerequisites
- Visual Studio 2019 与 ASP.NET 和 Web 开发 工作负载
- .NET Core SDK 2.2 或更高版本
警告
如果使用 Visual Studio 2017,请参阅 dotnet/sdk 问题 #3124,以了解无法与 Visual Studio 一起使用的 .NET Core SDK 版本的信息。
Visual Studio Code 说明使用用于 ASP.NET Core 的 .NET Core CLI 开发功能,如项目创建。可在任何平台(macOS、Linux 或 Windows)上或在任何代码编辑器中遵循这些说明。如果使用 Visual Studio Code 以外的其他内容,则可能需要进行少量更改。
创建 Web 项目Create a web project
- 从“文件”菜单中选择“新建”>“项目” 。
- 选择“ASP.NET Core Web 应用程序”模板,再单击“下一步” 。
- 将项目命名为 TodoApi,然后单击“创建” 。
- 在“创建新的 ASP.NET Core Web 应用程序”对话框中,确认选择“.NET Core”和“ASP.NET Core 2.2” 。选择“API”模板,然后单击“创建” 。请不要选择“启用 Docker 支持” 。
打开集成终端。
将目录 (
cd
) 更改为包含项目文件夹的文件夹。运行以下命令:
dotnet new webapi -o TodoApi
code -r TodoApi
这些命令会创建新 Web API 项目并在新项目文件夹中打开 Visual Studio Code 的新实例。
- 当对话框询问是否要将所需资产添加到项目时,选择“是” 。
- 选择“文件”>“新建解决方案” 。
- 选择“.NET Core” >“应用” >“API” >“下一步”。
在“配置新的 ASP.NET Core Web API” 对话框中,接受默认的 *.NET Core 2.2 “目标框架” 。
输入“TodoApi” 作为“项目名称” ,然后选择“创建” 。
测试 APITest the API
项目模板会创建 values
API。从浏览器调用 Get
方法以测试应用。
按 Ctrl+F5 运行应用。Visual Studio 启动浏览器并导航到 https://localhost:<port>/api/values
,其中 <port>
是随机选择的端口号。
如果出现询问是否应信任 IIS Express 证书的对话框,则选择“是” 。在接下来出现的“安全警告” 对话框中,选择“是” 。
按 Ctrl+F5 运行应用。在浏览器中,转到以下 URL:https://localhost:5001/api/values。
选择“运行” > “开始调试” 以启动应用。Visual Studio for Mac 会启动浏览器并导航到 https://localhost:<port>
,其中 <port>
是随机选择的端口号。将返回 HTTP 404(未找到)错误。将 /api/values
追加到 URL(将 URL 更改为 https://localhost:<port>/api/values
)。
会返回以下 JSON:
["value1","value2"]
添加模型类Add a model class
模型 是一组表示应用管理的数据的类。此应用的模型是单个 TodoItem
类。
在“解决方案资源管理器” 中,右键单击项目。选择“添加” > “新建文件夹” 。将文件夹命名为“Models” 。
右键单击“Models” 文件夹,然后选择“添加” > “类” 。将类命名为 TodoItem,然后选择“添加” 。
将模板代码替换为以下代码:
添加名为“Models”的文件夹 。
使用以下代码将
TodoItem
类添加到 Models 文件夹:
- 右键单击项目。选择“添加” > “新建文件夹” 。将文件夹命名为“Models” 。
右键单击“Models” 文件夹,然后选择“添加” >“新建文件” >“常规” >“空类” 。
将类命名为“TodoItem”,然后单击“新建” 。
将模板代码替换为以下代码:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
Id
属性用作关系数据库中的唯一键。
模型类可位于项目的任意位置,但按照惯例会使用 Models 文件夹。
添加数据库上下文Add a database context
数据库上下文是为数据模型协调 Entity Framework 功能的主类 。此类由 Microsoft.EntityFrameworkCore.DbContext
类派生而来。
- 右键单击“Models” 文件夹,然后选择“添加” > “类” 。将类命名为 TodoContext,然后单击“添加” 。
- 将
TodoContext
类添加到“Models” 文件夹。
- 将模板代码替换为以下代码:
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
}
注册数据库上下文Register the database context
在 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 容器。
- 指定数据库上下文将使用内存中数据库。
添加控制器Add a controller
右键单击 Controllers 文件夹。
选择“添加”>“新项” 。
在“添加新项”对话框中,选择“API 控制器类”模板 。
将类命名为 TodoController,然后选择“添加” 。
- 在“控制器”文件夹中,创建名为
TodoController
的类 。
- 将模板代码替换为以下代码:
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 方法Add Get methods
若要提供检索待办事项的 API,请将以下方法添加到 TodoController
类中:
// 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}
如果应用仍在运行,请停止它。然后再次运行它以包括最新更改。
通过从浏览器调用两个终结点来测试应用。例如:
以下 HTTP 响应通过调用 GetTodoItems
来生成:
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
路由和 URL 路径Routing and URL paths
[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
参数中提供给方法。
// 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;
}
返回值Return values
GetTodoItems
和 GetTodoItem
方法的返回类型是 ActionResult<T> 类型。ASP.NET Core 自动将对象序列化为 JSON,并将 JSON 写入响应消息的正文中。在假设没有未经处理的异常的情况下,此返回类型的响应代码为 200。未经处理的异常将转换为 5xx 错误。
ActionResult
返回类型可以表示大范围的 HTTP 状态代码。例如,GetTodoItem
可以返回两个不同的状态值:
- 如果没有任何项与请求的 ID 匹配,则该方法将返回 404 NotFound 错误代码。
- 否则,此方法将返回具有 JSON 响应正文的 200。返回
item
则产生 HTTP 200 响应。
测试 GetTodoItems 方法Test the GetTodoItems method
本教程使用 Postman 测试 Web API。
- 安装 Postman。
- 启动 Web 应用。
- 启动 Postman。
- 禁用 SSL 证书验证。
- 在“文件” >“设置”(“常规” 选项卡)中,禁用“SSL 证书验证”。
- 在“Postman” > “首选项”(“常规”选项卡)中,禁用“SSL 证书验证” 。或者,选择扳手图标并选择“设置”,然后禁用“SSL 证书验证” 。
警告
在测试控制器之后重新启用 SSL 证书验证。
- 创建新请求。
- 将 HTTP 方法设置为“GET” 。
- 将请求 URL 设置为
https://localhost:<port>/api/todo
。例如https://localhost:5001/api/todo
。
- 在 Postman 中设置Two pane view 。
- 选择Send。
添加创建方法Add a Create method
在 Controllers / TodoController.cs 中添加以下 PostTodoItem
方法 :
// 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
调用中硬编码操作名称。
// 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 方法Test the PostTodoItem method
生成项目。
在 Postman 中,将 HTTP 方法设置为
POST
。选择“正文”选项卡 。
选择“原始”单选按钮 。
将类型设置为 JSON (application/json)
在请求正文中,输入待办事项的 JSON:
{
"name":"walk dog",
"isComplete":true
}
- 选择Send。
如果收到 405 不允许的方法错误,则可能是由于未在添加 PostTodoItem
方法之后编译项目。
测试位置标头 URITest the location header URI
在Headers 窗格中选择Response 选项卡。
复制Location 标头值:
将方法设置为“GET”。
粘贴 URI(例如,
https://localhost:5001/api/Todo/2
)。选择Send。
添加 PutTodoItem 方法Add a PutTodoItem method
添加以下 PutTodoItem
方法:
// 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 方法Test the PutTodoItem method
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。在进行 PUT 调用之前,数据库中必须有一个项。调用 GET,以确保在调用 PUT 之前数据库中存在项目。
更新 id = 1 的待办事项并将其名称设置为“feed fish”:
{
"ID":1,
"name":"feed fish",
"isComplete":true
}
下图显示 Postman 更新:
添加 DeleteTodoItem 方法Add a DeleteTodoItem method
添加以下 DeleteTodoItem
方法:
// 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 方法Test the DeleteTodoItem method
使用 Postman 删除待办事项:
- 将方法设置为
DELETE
。 - 设置要删除的对象的 URI,例如
https://localhost:5001/api/todo/1
。 - 选择Send。
可通过示例应用删除所有项。但如果删除最后一项,则在下次调用 API 时,模型类构造函数会创建一个新项。
使用 JavaScript 调用 Web APICall the web API with JavaScript
在本部分中,将添加一个 HTML 页面,该页面使用 JavaScript 调用 Web API。jQuery 可启动该请求。JavaScript 会使用 Web API 响应的详细信息来更新页面。
通过下面突出显示的代码更新 Startup.cs,配置应用来提供静态文件并实现默认文件映射 :
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.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
在项目目录中创建 wwwroot 文件夹。
将一个名为 index.html 的 HTML 文件添加到 wwwroot 目录 。用以下标记替代其内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
将名为 site.js 的 JavaScript 文件添加到 wwwroot 目录 。用以下代码替代其内容:
const uri = "api/todo";
let todos = null;
function getCount(data) {
const el = $("#counter");
let name = "to-do";
if (data) {
if (data > 1) {
name = "to-dos";
}
el.text(data + " " + name);
} else {
el.text("No " + name);
}
}
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function(key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function() {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function() {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function(key, item) {
if (item.id === id) {
$("#edit-name").val(item.name);
$("#edit-id").val(item.id);
$("#edit-isComplete")[0].checked = item.isComplete;
}
});
$("#spoiler").css({ display: "block" });
}
$(".my-form").on("submit", function() {
const item = {
name: $("#edit-name").val(),
isComplete: $("#edit-isComplete").is(":checked"),
id: $("#edit-id").val()
};
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$("#spoiler").css({ display: "none" });
}
可能需要更改 ASP.NET Core 项目的启动设置,以便对 HTML 页面进行本地测试:
- 打开 Properties\launchSettings.json 。
- 删除
launchUrl
以便在项目的默认文件 index.html 强制打开应用 。
此示例调用 Web API 的所有 CRUD 方法。以下是 API 调用的说明。
获取待办事项的列表Get a list of to-do items
jQuery 将向 Web API 发送 HTTP GET 请求,该 API 返回表示待办事项的数组的 JSON。如果请求成功,则调用 success
回调函数。在该回调中使用待办事项信息更新 DOM。
$(document).ready(function() {
getData();
});
function getData() {
$.ajax({
type: "GET",
url: uri,
cache: false,
success: function(data) {
const tBody = $("#todos");
$(tBody).empty();
getCount(data.length);
$.each(data, function(key, item) {
const tr = $("<tr></tr>")
.append(
$("<td></td>").append(
$("<input/>", {
type: "checkbox",
disabled: true,
checked: item.isComplete
})
)
)
.append($("<td></td>").text(item.name))
.append(
$("<td></td>").append(
$("<button>Edit</button>").on("click", function() {
editItem(item.id);
})
)
)
.append(
$("<td></td>").append(
$("<button>Delete</button>").on("click", function() {
deleteItem(item.id);
})
)
);
tr.appendTo(tBody);
});
todos = data;
}
});
}
添加待办事项Add a to-do item
jQuery 发送 HTTP POST 请求,请求正文中包含待办事项。将 accepts
和 contentType
选项设置为 application/json
,以便指定接收和发送的媒体类型。待办事项使用 JSON.stringify 转换为 JSON。当 API 返回成功状态的代码时,将调用 getData
函数来更新 HTML 表。
function addItem() {
const item = {
name: $("#add-name").val(),
isComplete: false
};
$.ajax({
type: "POST",
accepts: "application/json",
url: uri,
contentType: "application/json",
data: JSON.stringify(item),
error: function(jqXHR, textStatus, errorThrown) {
alert("Something went wrong!");
},
success: function(result) {
getData();
$("#add-name").val("");
}
});
}
更新待办事项Update a to-do item
更新待办事项与添加类似。url
更改为添加项的唯一标识符,并且 type
为 PUT
。
$.ajax({
url: uri + "/" + $("#edit-id").val(),
type: "PUT",
accepts: "application/json",
contentType: "application/json",
data: JSON.stringify(item),
success: function(result) {
getData();
}
});
删除待办事项Delete a to-do item
若要删除待办事项,请将 AJAX 调用上的 type
设为 DELETE
并指定该项在 URL 中的唯一标识符。
$.ajax({
url: uri + "/" + id,
type: "DELETE",
success: function(result) {
getData();
}
});
向 Web API 添加身份验证支持Add authentication support to a web API
ASP.NET Core 标识将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。若要保护 Web API 和 SPA,请使用以下项之一:
- Azure Active Directory
- Azure Active Directory B2C (Azure AD B2C)]
- IdentityServer4
IdentityServer4 是适用于 ASP.NET Core 3.0 的 OpenID Connect 和 OAuth 2.0 框架。IdentityServer4 支持以下安全功能:
- 身份验证即服务 (AaaS)
- 跨多个应用程序类型的单一登录/注销 (SSO)
- API 的访问控制
- Federation Gateway
有关详细信息,请参阅欢迎使用 IdentityServer4。
其他资源Additional resources
查看或下载本教程的示例代码。请参阅如何下载。
有关更多信息,请参见以下资源: