Note
此版本不是本文的最新版本。 对于当前版本,请参阅本文的 .NET 9 版本。
Warning
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 NET Core 支持策略。 对于当前版本,请参阅本文的 .NET 9 版本。
Important
此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。
对于当前版本,请参阅本文的 .NET 9 版本。
本教程介绍生成使用数据库的基于控制器的 Web API 的基础知识。 在 ASP.NET Core 中创建 API 的另一种方法是创建 最少的 API。 有关在最小 API 和基于控制器的 API 之间进行选择的帮助,请参阅 API 概述。 有关创建最小 API 的教程,请参阅教程:使用 ASP.NET Core 创建最小 API。
Overview
本教程将创建以下 API:
| API | Description | 请求主体 | 响应体 | 
|---|---|---|---|
| GET /api/todoitems | 获取所有待办事项 | None | 待办事项的数组 | 
| GET /api/todoitems/{id} | 按 ID 获取项 | None | To-do 项 | 
| POST /api/todoitems | 添加新项 | To-do 项 | To-do 项 | 
| PUT /api/todoitems/{id} | 更新现有项 | To-do 项 | None | 
| DELETE /api/todoitems/{id} | 删除项 | None | None | 
下图显示了应用的设计。
               
              
            
Prerequisites
- Visual Studio 2022 与“ASP.NET 和 Web 开发”工作负载。   
创建 Web API 项目
- 在“文件”菜单中,选择 “新建>项目”。
- 在搜索框中输入 Web API。
- 选择“ASP.NET Core Web API”模板,然后选择“下一步”。
- 在“配置新项目”对话框中,将项目命名为“TodoApi”,然后选择“下一步”。
- 在“其他信息”对话框中:- 确认 Framework 为 .NET 9.0(标准期限支持)。
- 确认已选中“启用 OpenAPI 支持”复选框。
- 确认已选中“使用控制器(取消选中以使用最小 API)”复选框。
- 选择 创建。
 
添加 NuGet 包
必须添加 NuGet 包以支持本教程中使用的数据库。
- 在“工具”菜单中,选择“NuGet 包管理器”“管理解决方案的 NuGet 包”。
- 选择“浏览”选项卡。
- 在搜索框中输入“Microsoft.EntityFrameworkCore.InMemory”,然后选择 。
- 选中右窗格中的“项目”复选框,然后选择“安装” 。
Note
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
运行项目
项目模板创建 WeatherForecast 支持 OpenAPI 的 API。
按 Ctrl+F5 以在不使用调试程序的情况下运行。
如果尚未将项目配置为使用 SSL,Visual Studio 显示以下对话:
               
              
            
如果信任 IIS Express SSL 证书,请选择“是”。
将显示以下对话框:
               
              
            
如果你同意信任开发证书,请选择“是”。
有关信任 Firefox 浏览器的信息,请参阅 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 证书错误。
Visual Studio 启动一个终端窗口,并显示正在运行的应用的 URL。 API 托管在 https://localhost:<port>,其中 <port> 是在项目创建时随机选择的端口号。
...
info: Microsoft.Hosting.Lifetime[14]
   Now listening on: https://localhost:7260
info: Microsoft.Hosting.Lifetime[14]
   Now listening on: http://localhost:7261
info: Microsoft.Hosting.Lifetime[0]
   Application started. Press Ctrl+C to shut down.
...
              Ctrl+单击 输出中的 HTTPS URL 以在浏览器中测试 Web 应用。 
              https://localhost:<port> 没有终结点,因此浏览器返回 HTTP 404 未找到。
将 /weatherforecast 附加到 URL,以测试 WeatherForecast API。
浏览器显示类似于以下示例的 JSON:
[
    {
        "date": "2025-07-16",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2025-07-17",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2025-07-18",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2025-07-19",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2025-07-20",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]
测试项目
本教程使用终结点资源管理器和 .http 文件来测试 API。
添加模型类
              模型是一组类,表示应用管理的数据。 此应用的模型是 TodoItem 类。
- 在 解决方案资源管理器中,右键单击项目。 选择“添加>新文件夹。 将该文件夹命名为 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 文件夹。
添加数据库上下文
数据库上下文是协调数据模型的 Entity Framework 功能的主类。 此类由 Microsoft.EntityFrameworkCore.DbContext 类派生而来。
- 右键单击 - Models文件夹,然后选择“ 添加>类”。 将类命名 为 TodoContext ,然后单击“ 添加”。
- 输入以下代码: - using Microsoft.EntityFrameworkCore; namespace TodoApi.Models; public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } = null!; }
注册数据库上下文
在 ASP.NET Core 中,服务(如数据库上下文)必须向依赖关系注入 (DI) 容器进行注册。 该容器向控制器提供服务。
使用以下突出显示的代码更新 Program.cs:
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
前面的代码:
- 添加 using指令。
- 将数据库上下文添加到 DI 容器。
- 指定数据库上下文将使用内存中数据库。
构建控制器
- 右键单击 - Controllers文件夹。
- 选择 添加>New Scaffolded Item。 
- 选择“其操作使用实体框架的 API 控制器”,然后选择“添加”。 
- 在“添加其操作使用实体框架的 API 控制器”对话框中: - 在 Model 类中选择 TodoItem (TodoApi.Models)。
- 在数据上下文类中选择 TodoContext (TodoApi.Models)。
- 选择 并添加。
 - 如果搭建基架失败,请选择添加以尝试第二次搭建。 
此步骤将 Microsoft.VisualStudio.Web.CodeGeneration.Design 和 Microsoft.EntityFrameworkCore.Tools NuGet 包添加到项目中。
这些包是基架所需的。
生成的代码:
- 使用 [ApiController]属性标记类。 此属性指示控制器响应 Web API 请求。 有关该属性启用的特定行为的信息,请参阅使用 ASP.NET Core 创建 Web API。
- 使用 DI 将数据库上下文 (TodoContext) 注入到控制器中。 数据库上下文用于控制器中的每个 CRUD 方法。
ASP.NET Core 模板:
- 具有视图的控制器在路由模板中包含 [action]。
- API 控制器不在路由模板中包含 [action]。
如果[action]标记不在路由模板中,则终结点中不会包含操作名称(方法名称)。 也就是说,不会在匹配的路由中使用操作的关联方法名称。
更新 PostTodoItem create 方法
将 PostTodoItem 中的返回语句更新为使用 nameof 运算符:
[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 请求正文获取 TodoItem 的值。
有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
CreatedAtAction 方法:
- 如果成功,将返回 HTTP 201 状态代码。 
              HTTP 201是在服务器上创建新资源的HTTP POST方法的标准响应。
- 向响应添加 Location 标头。 标头 Location指定新创建的 to-do 项的 URI 。 有关详细信息,请参阅创建的 10.2.2 201。
- 引用 GetTodoItem操作以创建Location标头的 URI。 C#nameof关键字用于避免在CreatedAtAction调用中硬编码操作名称。
测试 PostTodoItem
- 选择查看>其他窗口>端点浏览器。 
- 右键单击“POST”终结点,然后选择“生成请求”。   - 在名为 - TodoApi.http的项目文件夹中创建一个新文件,其内容类似于以下示例:- @TodoApi_HostAddress = https://localhost:49738 POST {{TodoApi_HostAddress}}/api/todoitems Content-Type: application/json { //TodoItem } ###- 第一行创建了一个变量,该变量适用于所有终结点。
- 下一行定义了 POST 请求。
- POST 请求行后面的行会定义标头以及请求正文的占位符。
- 三重井号标签 (###) 行是请求分隔符:对于不同的请求,该标签之后的内容属于另一个请求。
 
- POST 请求需要 - TodoItem。 要定义待办事项,请将- //TodoItem注释替换为以下 JSON:- { "name": "walk dog", "isComplete": true }- TodoApi.http 文件现在应如以下示例所示,但带有端口号: - @TodoApi_HostAddress = https://localhost:7260 Post {{TodoApi_HostAddress}}/api/todoitems Content-Type: application/json { "name": "walk dog", "isComplete": true } ###
- 运行应用。 
- 选择 请求行上方的“发送请求” - POST链接。  - POST 请求将发送到应用,响应将显示在“响应”窗格中。   
测试位置标头 URI
通过从浏览器调用 GET 终结点或使用 终结点探查器来测试应用。 以下步骤适用于终结点资源管理器。
- 在“终结点资源管理器”中,右键单击第一个 GET 终结点,然后选择“生成请求”。 - 将以下内容添加到 - TodoApi.http文件中:- GET {{TodoApi_HostAddress}}/api/todoitems ###
- 选择新的 请求行上方的“发送请求” - GET链接。- GET 请求将发送到应用,响应将显示在“响应”窗格中。 
- 响应正文与以下 JSON 类似: - [ { "id": 1, "name": "walk dog", "isComplete": true } ]
- 在终结点资源管理器中,右键单击 - /api/todoitems/{id}GET终结点,然后选择生成请求。 将以下内容添加到- TodoApi.http文件中:- @id=0 GET {{TodoApi_HostAddress}}/api/todoitems/{{id}} ###
- 将 - {@id}分配给- 1(而不是- 0)。
- 选择新的 GET 请求行上方的“发送请求”链接。 - GET 请求将发送到应用,响应将显示在“响应”窗格中。 
- 响应正文与以下 JSON 类似: - { "id": 1, "name": "walk dog", "isComplete": true }
检查 GET 方法
实现了两个 GET 终结点:
- GET /api/todoitems
- GET /api/todoitems/{id}
上一节展示了 /api/todoitems/{id} 路由的示例。
按照 POST 说明添加另一个待办事项,然后使用 Swagger 测试 /api/todoitems 路由。
此应用使用内存中数据库。 如果停止并启动应用,则前面的 GET 请求不会返回任何数据。 如果未返回任何数据,则向应用程序发送 POST 数据。
路由和 URL 路径
              [HttpGet] 属性表示响应 HTTP GET 请求的方法。 每个方法的 URL 路径构造如下所示:
- 在控制器的 - Route属性中以模板字符串开头:- [Route("api/[controller]")] [ApiController] public class TodoItemsController : ControllerBase
- 将 - [controller]替换为控制器的名称,按照惯例,在控制器类名称中去掉“Controller”后缀。 对于此示例,控制器类名称为 TodoItems控制器,因此控制器名称为“TodoItems”。 ASP.NET 核心 路由 不区分大小写。
- 如果 - [HttpGet]属性具有路由模板(例如- [HttpGet("products")]),则将它追加到路径。 此示例不使用模板。 有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
在下面的 GetTodoItem 方法中,"{id}" 是待办事项的唯一标识符的占位符变量。 调用 GetTodoItem 时,URL 中 "{id}" 的值会在 id 参数中提供给方法。
[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 方法
检查 PutTodoItem 方法:
[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 方法
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。 在进行 PUT 调用之前,数据库中必须有一个项。 调用 GET,以确保在调用 PUT 之前数据库中存在项。
使用 PUT 方法更新 Id=1 的 TodoItem,并将其名称设置为 "feed fish"。 请注意,响应为 HTTP 204 No Content。
- 在“终结点资源管理器”中,右键单击 PUT 终结点,然后选择“生成请求”。 - 将以下内容添加到 - TodoApi.http文件中:- PUT {{TodoApi_HostAddress}}/api/todoitems/{{id}} Content-Type: application/json { //TodoItem } ###
- 在 PUT 请求行中,将 - {{id}}替换为- 1。
- 将 - //TodoItem占位符替换为以下行:- PUT {{TodoApi_HostAddress}}/api/todoitems/1 Content-Type: application/json { "id": 1, "name": "feed fish", "isComplete": false }
- 选择新的 PUT 请求行上方的“发送请求”链接。 - PUT 请求将发送到应用,响应将显示在“响应”窗格中。 响应正文为空,状态代码为 204。 
DeleteTodoItem 方法
检查 DeleteTodoItem 方法:
[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 方法
使用 DELETE 方法删除 Id = 1 的 TodoItem。 请注意,响应为 HTTP 204 No Content。
- 在“端点资源管理器”中,右键单击“删除”端点,然后选择“生成请求”。 - 将 DELETE 请求添加到 - TodoApi.http。
- 将 DELETE 请求行中的 - {{id}}替换为- 1。 DELETE 请求应如以下示例所示:- DELETE {{TodoApi_HostAddress}}/api/todoitems/{{id}} ###
- 选择 DELETE 请求的“发送请求”链接。 - DELETE 请求将发送到应用,响应将显示在“响应”窗格中。 响应正文为空,状态代码为 204。 
使用其他工具进行测试
还有许多其他工具可用于测试 Web API,例如:
防止过度发布
目前,示例应用公开了整个 TodoItem 对象。 生产应用通常使用模型的子集来限制输入和返回的数据。 这背后有多种原因,但安全性是主要原因。 模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。 本教程使用 DTO。
DTO 可用于:
- 防止过度发布。
- 隐藏客户端不应查看的属性。
- 省略一些属性以缩减有效负载大小。
- 平展包含嵌套对象的对象图。 对客户端而言,平展的对象图可能更方便。
若要演示 DTO 方法,请更新 TodoItem 类,使其包含机密字段:
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; }
}
此应用需要隐藏机密字段,但管理应用可以选择公开它。
确保可以发布和获取机密字段。
在 Models/TodoItemsDTO.cs 文件中创建 DTO 模型:
namespace TodoApi.Models;
public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}
更新 TodoItemsController 以使用 TodoItemDTO:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;
    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }
    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }
    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [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);
    }
    // </snippet_GetByID>
    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }
        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }
        return NoContent();
    }
    // </snippet_Update>
    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };
        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();
        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>
    // 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();
    }
    private bool TodoItemExists(long id)
    {
        return _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 API
请参阅教程:使用 JavaScript 调用 ASP.NET Core Web API。
Web API 视频系列
请参阅视频:初学者系列:Web API。
企业 Web 应用模式
有关创建可靠、安全、高性能、可测试且可缩放的 ASP.NET Core 应用的指导,请参阅 企业 Web 应用模式。 提供了一个完整且具备生产质量的示例 Web 应用,该应用实现了这些模式。
向 Web API 添加身份验证支持
ASP.NET Core Identity 将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。 若要保护 Web API 和 SPA,请使用以下项之一:
- Microsoft Entra ID
- Azure Active Directory B2C (Azure AD B2C)
- Duende Identity 服务器
Duende Identity Server 是适用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。 Duende Identity Server 支持以下安全功能:
- 身份验证即服务 (AaaS)
- 跨多个应用程序类型的单一登录/注销 (SSO)
- API 的访问控制
- 联邦网关
Important
Duende Software 可能会要求你为 Duende Identity Server 的生产使用支付许可证费用。 有关详细信息,请参阅 从 .NET 5 中的 ASP.NET Core 迁移到 .NET 6。
有关详细信息,请参阅 Duende Identity Server 文档(Duende Software 网站)。
发布到 Azure
有关部署到 Azure 的信息,请参阅快速入门:部署 ASP.NET Web 应用。
其他资源
查看或下载本教程的示例代码。 请参阅如何下载。
有关详细信息,请参阅以下资源:
- 使用 ASP.NET Core 创建 Web API
- 教程:使用 ASP.NET Core 创建最小 API
- 使用生成的 OpenAPI 文档
- 带有 Swagger/OpenAPI 的 ASP.NET Core Web API 文档
- Razor ASP.NET Core 中带 Entity Framework Core 的页面 - 第 1 个教程(共 8 个)
- 在 ASP.NET Core 中路由到控制器操作
- ASP.NET Core Web API 中控制器操作的返回类型
- 将 ASP.NET Core 应用部署到 Azure 应用程序服务
- 托管和部署 ASP.NET Core
- 使用 ASP.NET Core 创建 Web API
本教程介绍生成使用数据库的基于控制器的 Web API 的基础知识。 在 ASP.NET Core 中创建 API 的另一种方法是创建 最少的 API。 有关在最小 API 和基于控制器的 API 之间进行选择的帮助,请参阅 API 概述。 有关创建最小 API 的教程,请参阅教程:使用 ASP.NET Core 创建最小 API。
Overview
本教程将创建以下 API:
| API | Description | 请求主体 | 响应体 | 
|---|---|---|---|
| GET /api/todoitems | 获取所有待办事项 | None | 待办事项的数组 | 
| GET /api/todoitems/{id} | 按 ID 获取项 | None | To-do 项 | 
| POST /api/todoitems | 添加新项 | To-do 项 | To-do 项 | 
| PUT /api/todoitems/{id} | 更新现有项 | To-do 项 | None | 
| DELETE /api/todoitems/{id} | 删除项 | None | None | 
下图显示了应用的设计。
               
              
            
Prerequisites
- Visual Studio 2022 与“ASP.NET 和 Web 开发”工作负载。   
创建 Web 项目
- 在“文件”菜单中,选择 “新建>项目”。
- 在搜索框中输入 Web API。
- 选择“ASP.NET Core Web API”模板,然后选择“下一步”。
- 在“配置新项目”对话框中,将项目命名为“TodoApi”,然后选择“下一步”。
- 在“其他信息”对话框中:- 确认框架为 .NET 8.0(长期支持)。
- 确认已选中“使用控制器(取消选中以使用最小 API)”复选框。
- 确认已选中“启用 OpenAPI 支持”复选框。
- 选择 创建。
 
添加 NuGet 包
必须添加 NuGet 包以支持本教程中使用的数据库。
- 在“工具”菜单中,选择“NuGet 包管理器”“管理解决方案的 NuGet 包”。
- 选择“浏览”选项卡。
- 在搜索框中输入“Microsoft.EntityFrameworkCore.InMemory”,然后选择 。
- 选中右窗格中的“项目”复选框,然后选择“安装” 。
Note
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
测试项目
项目模板创建了一个支持 WeatherForecast 的  API。
按 Ctrl+F5 以在不使用调试程序的情况下运行。
如果尚未将项目配置为使用 SSL,Visual Studio 显示以下对话:
               
              
            
如果信任 IIS Express SSL 证书,请选择“是”。
将显示以下对话框:
               
              
            
如果你同意信任开发证书,请选择“是”。
有关信任 Firefox 浏览器的信息,请参阅 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 证书错误。
Visual Studio 将启动默认浏览器并导航到 https://localhost:<port>/swagger/index.html,其中 <port> 是在创建项目时设置的一个随机选择的端口号。
随即显示 Swagger 页面 /swagger/index.html。 选择 "GET">尝试>执行。 页面将显示:
- 用于测试 WeatherForecast API 的 Curl 命令。
- 用于测试 WeatherForecast API 的 URL。
- 响应代码、正文和标头。
- 包含媒体类型、示例值和架构的下拉列表框。
如果 Swagger 页面未显示,请参阅此 GitHub 问题。
Swagger 用于为 Web API 生成有用的文档和帮助页面。 本教程使用 Swagger 测试应用。 有关 Swagger 的详细信息,请参阅包含 Swagger / OpenAPI 的 ASP.NET Core Web API 文档。
在浏览器中复制并粘贴 请求 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"
    }
]
添加模型类
              模型是一组类,表示应用管理的数据。 此应用的模型是 TodoItem 类。
- 在 解决方案资源管理器中,右键单击项目。 选择“添加>新文件夹。 将该文件夹命名为 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 文件夹。
添加数据库上下文
数据库上下文是协调数据模型的 Entity Framework 功能的主类。 此类由 Microsoft.EntityFrameworkCore.DbContext 类派生而来。
- 右键单击 Models文件夹,然后选择“ 添加>类”。 将类命名 为 TodoContext ,然后单击“ 添加”。
- 输入以下代码: - using Microsoft.EntityFrameworkCore; namespace TodoApi.Models; public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } = null!; }
注册数据库上下文
在 ASP.NET Core 中,服务(如数据库上下文)必须向依赖关系注入 (DI) 容器进行注册。 该容器向控制器提供服务。
使用以下突出显示的代码更新 Program.cs:
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
前面的代码:
- 添加 using指令。
- 将数据库上下文添加到 DI 容器。
- 指定数据库上下文将使用内存中数据库。
构建控制器
- 右键单击 - Controllers文件夹。
- 选择 添加>New Scaffolded Item。 
- 选择“其操作使用实体框架的 API 控制器”,然后选择“添加”。 
- 在“添加其操作使用实体框架的 API 控制器”对话框中: - 在 Model 类中选择 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 create 方法
将 PostTodoItem 中的返回语句更新为使用 nameof 运算符:
[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 请求正文获取 TodoItem 的值。
有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
CreatedAtAction 方法:
- 如果成功,将返回 HTTP 201 状态代码。 
              HTTP 201是在服务器上创建新资源的HTTP POST方法的标准响应。
- 向响应添加 Location 标头。 标头 Location指定新创建的 to-do 项的 URI 。 有关详细信息,请参阅创建的 10.2.2 201。
- 引用 GetTodoItem操作以创建Location标头的 URI。 C#nameof关键字用于避免在CreatedAtAction调用中硬编码操作名称。
测试 PostTodoItem
- 按 Ctrl+F5 运行应用。 
- 在 Swagger 浏览器窗口中,选择 POST /api/TodoItems,然后选择“ 试用”。 
- 在 “请求正文 输入”窗口中,更新 JSON。 例如, - { "name": "walk dog", "isComplete": true }
- 选择“执行”   
测试位置标头 URI
在前面的 POST 中,Swagger UI 在“响应”标头下显示位置标头。 例如,location: https://localhost:7260/api/TodoItems/1。 位置标头显示创建资源的 URI。
测试位置标头:
- 在 Swagger 浏览器窗口中,选择 GET /api/TodoItems/{id},然后选择“ 试用”。 
- 在输入框中输入 - 1,然后选择“- id”。  
检查 GET 方法
实现了两个 GET 终结点:
- GET /api/todoitems
- GET /api/todoitems/{id}
上一节展示了 /api/todoitems/{id} 路由的示例。
按照 POST 说明添加另一个待办事项,然后使用 Swagger 测试 /api/todoitems 路由。
此应用使用内存中数据库。 如果停止并启动应用,则前面的 GET 请求不会返回任何数据。 如果未返回任何数据,则向应用程序发送 POST 数据。
路由和 URL 路径
              [HttpGet] 属性表示响应 HTTP GET 请求的方法。 每个方法的 URL 路径构造如下所示:
- 在控制器的 - Route属性中以模板字符串开头:- [Route("api/[controller]")] [ApiController] public class TodoItemsController : ControllerBase
- 将 - [controller]替换为控制器的名称,按照惯例,在控制器类名称中去掉“Controller”后缀。 对于此示例,控制器类名称为 TodoItems控制器,因此控制器名称为“TodoItems”。 ASP.NET 核心 路由 不区分大小写。
- 如果 - [HttpGet]属性具有路由模板(例如- [HttpGet("products")]),则将它追加到路径。 此示例不使用模板。 有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
在下面的 GetTodoItem 方法中,"{id}" 是待办事项的唯一标识符的占位符变量。 调用 GetTodoItem 时,URL 中 "{id}" 的值会在 id 参数中提供给方法。
[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 方法
检查 PutTodoItem 方法:
[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 方法
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。 在进行 PUT 调用之前,数据库中必须有一个项。 调用 GET,以确保在调用 PUT 之前数据库中存在项。
使用 Swagger UI,使用 PUT 按钮更新 Id = 1 的 TodoItem 并将其名称设置为 "feed fish"。 请注意,响应为 HTTP 204 No Content。
DeleteTodoItem 方法
检查 DeleteTodoItem 方法:
[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 方法
使用 Swagger UI 删除 Id = 1 的 TodoItem。 请注意,响应为 HTTP 204 No Content。
使用其他工具进行测试
还有许多其他工具可用于测试 Web API,例如:
- Visual Studio 终结点资源管理器和 .http 文件
- http-repl
- 
              curl. Swagger 使用 curl并显示它提交的curl命令。
- Fiddler
有关详细信息,请参阅:
防止过度发布
目前,示例应用公开了整个 TodoItem 对象。 生产应用通常使用模型的子集来限制输入和返回的数据。 这背后有多种原因,但安全性是主要原因。 模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。 本教程使用 DTO。
DTO 可用于:
- 防止过度发布。
- 隐藏客户端不应查看的属性。
- 省略一些属性以缩减有效负载大小。
- 平展包含嵌套对象的对象图。 对客户端而言,平展的对象图可能更方便。
若要演示 DTO 方法,请更新 TodoItem 类,使其包含机密字段:
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 模型:
namespace TodoApi.Models;
public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}
更新 TodoItemsController 以使用 TodoItemDTO:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;
    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }
    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }
    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [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);
    }
    // </snippet_GetByID>
    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }
        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }
        return NoContent();
    }
    // </snippet_Update>
    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };
        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();
        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>
    // 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();
    }
    private bool TodoItemExists(long id)
    {
        return _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 API
请参阅教程:使用 JavaScript 调用 ASP.NET Core Web API。
Web API 视频系列
请参阅视频:初学者系列:Web API。
企业 Web 应用模式
有关创建可靠、安全、高性能、可测试且可缩放的 ASP.NET Core 应用的指导,请参阅 企业 Web 应用模式。 提供了一个完整且具备生产质量的示例 Web 应用,该应用实现了这些模式。
向 Web API 添加身份验证支持
ASP.NET Core Identity 将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。 若要保护 Web API 和 SPA,请使用以下项之一:
- Microsoft Entra ID
- Azure Active Directory B2C (Azure AD B2C)
- Duende Identity 服务器
Duende Identity Server 是适用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。 Duende Identity Server 支持以下安全功能:
- 身份验证即服务 (AaaS)
- 跨多个应用程序类型的单一登录/注销 (SSO)
- API 的访问控制
- 联邦网关
Important
Duende Software 可能会要求你为 Duende Identity Server 的生产使用支付许可证费用。 有关详细信息,请参阅 从 .NET 5 中的 ASP.NET Core 迁移到 .NET 6。
有关详细信息,请参阅 Duende Identity Server 文档(Duende Software 网站)。
发布到 Azure
有关部署到 Azure 的信息,请参阅快速入门:部署 ASP.NET Web 应用。
其他资源
查看或下载本教程的示例代码。 请参阅如何下载。
有关详细信息,请参阅以下资源:
- 使用 ASP.NET Core 创建 Web API
- 教程:使用 ASP.NET Core 创建最小 API
- 带有 Swagger/OpenAPI 的 ASP.NET Core Web API 文档
- Razor ASP.NET Core 中带 Entity Framework Core 的页面 - 第 1 个教程(共 8 个)
- 在 ASP.NET Core 中路由到控制器操作
- ASP.NET Core Web API 中控制器操作的返回类型
- 将 ASP.NET Core 应用部署到 Azure 应用程序服务
- 托管和部署 ASP.NET Core
- 使用 ASP.NET Core 创建 Web API
本教程介绍生成使用数据库的基于控制器的 Web API 的基础知识。 在 ASP.NET Core 中创建 API 的另一种方法是创建 最少的 API。 有关在最小 API 和基于控制器的 API 之间进行选择的帮助,请参阅 API 概述。 有关创建最小 API 的教程,请参阅教程:使用 ASP.NET Core 创建最小 API。
Overview
本教程将创建以下 API:
| API | Description | 请求主体 | 响应体 | 
|---|---|---|---|
| GET /api/todoitems | 获取所有待办事项 | None | 待办事项的数组 | 
| GET /api/todoitems/{id} | 按 ID 获取项 | None | To-do 项 | 
| POST /api/todoitems | 添加新项 | To-do 项 | To-do 项 | 
| PUT /api/todoitems/{id} | 更新现有项 | To-do 项 | None | 
| DELETE /api/todoitems/{id} | 删除项 | None | None | 
下图显示了应用的设计。
               
              
            
Prerequisites
- Visual Studio 2022 与“ASP.NET 和 Web 开发”工作负载。   
创建 Web 项目
- 在“文件”菜单中,选择 “新建>项目”。
- 在搜索框中输入 Web API。
- 选择“ASP.NET Core Web API”模板,然后选择“下一步”。
- 在“配置新项目”对话框中,将项目命名为“TodoApi”,然后选择“下一步”。
- 在“其他信息”对话框中:- 确认框架为 .NET 8.0(长期支持)。
- 确认已选中“使用控制器(取消选中以使用最小 API)”复选框。
- 确认已选中“启用 OpenAPI 支持”复选框。
- 选择 创建。
 
添加 NuGet 包
必须添加 NuGet 包以支持本教程中使用的数据库。
- 在“工具”菜单中,选择“NuGet 包管理器”“管理解决方案的 NuGet 包”。
- 选择“浏览”选项卡。
- 在搜索框中输入“Microsoft.EntityFrameworkCore.InMemory”,然后选择 。
- 选中右窗格中的“项目”复选框,然后选择“安装” 。
Note
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
测试项目
项目模板创建了一个支持 WeatherForecast 的  API。
按 Ctrl+F5 以在不使用调试程序的情况下运行。
如果尚未将项目配置为使用 SSL,Visual Studio 显示以下对话:
               
              
            
如果信任 IIS Express SSL 证书,请选择“是”。
将显示以下对话框:
               
              
            
如果你同意信任开发证书,请选择“是”。
有关信任 Firefox 浏览器的信息,请参阅 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 证书错误。
Visual Studio 将启动默认浏览器并导航到 https://localhost:<port>/swagger/index.html,其中 <port> 是在创建项目时设置的一个随机选择的端口号。
随即显示 Swagger 页面 /swagger/index.html。 选择 "GET">尝试>执行。 页面将显示:
- 用于测试 WeatherForecast API 的 Curl 命令。
- 用于测试 WeatherForecast API 的 URL。
- 响应代码、正文和标头。
- 包含媒体类型、示例值和架构的下拉列表框。
如果 Swagger 页面未显示,请参阅此 GitHub 问题。
Swagger 用于为 Web API 生成有用的文档和帮助页面。 本教程使用 Swagger 测试应用。 有关 Swagger 的详细信息,请参阅包含 Swagger / OpenAPI 的 ASP.NET Core Web API 文档。
在浏览器中复制并粘贴 请求 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"
    }
]
添加模型类
              模型是一组类,表示应用管理的数据。 此应用的模型是 TodoItem 类。
- 在 解决方案资源管理器中,右键单击项目。 选择“添加>新文件夹。 将该文件夹命名为 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 文件夹。
添加数据库上下文
数据库上下文是协调数据模型的 Entity Framework 功能的主类。 此类由 Microsoft.EntityFrameworkCore.DbContext 类派生而来。
- 右键单击 Models文件夹,然后选择“ 添加>类”。 将类命名 为 TodoContext ,然后单击“ 添加”。
- 输入以下代码: - using Microsoft.EntityFrameworkCore; namespace TodoApi.Models; public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } = null!; }
注册数据库上下文
在 ASP.NET Core 中,服务(如数据库上下文)必须向依赖关系注入 (DI) 容器进行注册。 该容器向控制器提供服务。
使用以下突出显示的代码更新 Program.cs:
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
前面的代码:
- 添加 using指令。
- 将数据库上下文添加到 DI 容器。
- 指定数据库上下文将使用内存中数据库。
构建控制器
- 右键单击 - Controllers文件夹。
- 选择 添加>New Scaffolded Item。 
- 选择“其操作使用实体框架的 API 控制器”,然后选择“添加”。 
- 在“添加其操作使用实体框架的 API 控制器”对话框中: - 在 Model 类中选择 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 create 方法
将 PostTodoItem 中的返回语句更新为使用 nameof 运算符:
[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 请求正文获取 TodoItem 的值。
有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
CreatedAtAction 方法:
- 如果成功,将返回 HTTP 201 状态代码。 
              HTTP 201是在服务器上创建新资源的HTTP POST方法的标准响应。
- 向响应添加 Location 标头。 标头 Location指定新创建的 to-do 项的 URI 。 有关详细信息,请参阅创建的 10.2.2 201。
- 引用 GetTodoItem操作以创建Location标头的 URI。 C#nameof关键字用于避免在CreatedAtAction调用中硬编码操作名称。
测试 PostTodoItem
- 按 Ctrl+F5 运行应用。 
- 在 Swagger 浏览器窗口中,选择 POST /api/TodoItems,然后选择“ 试用”。 
- 在 “请求正文 输入”窗口中,更新 JSON。 例如, - { "name": "walk dog", "isComplete": true }
- 选择“执行”   
测试位置标头 URI
在前面的 POST 中,Swagger UI 在“响应”标头下显示位置标头。 例如,location: https://localhost:7260/api/TodoItems/1。 位置标头显示创建资源的 URI。
测试位置标头:
- 在 Swagger 浏览器窗口中,选择 GET /api/TodoItems/{id},然后选择“ 试用”。 
- 在输入框中输入 - 1,然后选择“- id”。  
检查 GET 方法
实现了两个 GET 终结点:
- GET /api/todoitems
- GET /api/todoitems/{id}
上一节展示了 /api/todoitems/{id} 路由的示例。
按照 POST 说明添加另一个待办事项,然后使用 Swagger 测试 /api/todoitems 路由。
此应用使用内存中数据库。 如果停止并启动应用,则前面的 GET 请求不会返回任何数据。 如果未返回任何数据,则向应用程序发送 POST 数据。
路由和 URL 路径
              [HttpGet] 属性表示响应 HTTP GET 请求的方法。 每个方法的 URL 路径构造如下所示:
- 在控制器的 - Route属性中以模板字符串开头:- [Route("api/[controller]")] [ApiController] public class TodoItemsController : ControllerBase
- 将 - [controller]替换为控制器的名称,按照惯例,在控制器类名称中去掉“Controller”后缀。 对于此示例,控制器类名称为 TodoItems控制器,因此控制器名称为“TodoItems”。 ASP.NET 核心 路由 不区分大小写。
- 如果 - [HttpGet]属性具有路由模板(例如- [HttpGet("products")]),则将它追加到路径。 此示例不使用模板。 有关详细信息,请参阅使用 Http [Verb] 特性的特性路由。
在下面的 GetTodoItem 方法中,"{id}" 是待办事项的唯一标识符的占位符变量。 调用 GetTodoItem 时,URL 中 "{id}" 的值会在 id 参数中提供给方法。
[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 方法
检查 PutTodoItem 方法:
[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 方法
本示例使用内存内、数据库,每次启动应用时都必须对其进行初始化。 在进行 PUT 调用之前,数据库中必须有一个项。 调用 GET,以确保在调用 PUT 之前数据库中存在项。
使用 Swagger UI,使用 PUT 按钮更新 Id = 1 的 TodoItem 并将其名称设置为 "feed fish"。 请注意,响应为 HTTP 204 No Content。
DeleteTodoItem 方法
检查 DeleteTodoItem 方法:
[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 方法
使用 Swagger UI 删除 Id = 1 的 TodoItem。 请注意,响应为 HTTP 204 No Content。
使用其他工具进行测试
还有许多其他工具可用于测试 Web API,例如:
- Visual Studio 终结点资源管理器和 .http 文件
- http-repl
- 
              curl. Swagger 使用 curl并显示它提交的curl命令。
- Fiddler
有关详细信息,请参阅:
防止过度发布
目前,示例应用公开了整个 TodoItem 对象。 生产应用通常使用模型的子集来限制输入和返回的数据。 这背后有多种原因,但安全性是主要原因。 模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。 本教程使用 DTO。
DTO 可用于:
- 防止过度发布。
- 隐藏客户端不应查看的属性。
- 省略一些属性以缩减有效负载大小。
- 平展包含嵌套对象的对象图。 对客户端而言,平展的对象图可能更方便。
若要演示 DTO 方法,请更新 TodoItem 类,使其包含机密字段:
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 模型:
namespace TodoApi.Models;
public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}
更新 TodoItemsController 以使用 TodoItemDTO:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;
    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }
    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }
    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [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);
    }
    // </snippet_GetByID>
    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }
        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }
        return NoContent();
    }
    // </snippet_Update>
    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };
        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();
        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>
    // 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();
    }
    private bool TodoItemExists(long id)
    {
        return _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 API
请参阅教程:使用 JavaScript 调用 ASP.NET Core Web API。
Web API 视频系列
请参阅视频:初学者系列:Web API。
企业 Web 应用模式
有关创建可靠、安全、高性能、可测试且可缩放的 ASP.NET Core 应用的指导,请参阅 企业 Web 应用模式。 提供了一个完整且具备生产质量的示例 Web 应用,该应用实现了这些模式。
向 Web API 添加身份验证支持
ASP.NET Core Identity 将用户界面 (UI) 登录功能添加到 ASP.NET Core Web 应用。 若要保护 Web API 和 SPA,请使用以下项之一:
- Microsoft Entra ID
- Azure Active Directory B2C (Azure AD B2C)
- Duende Identity 服务器
Duende Identity Server 是适用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。 Duende Identity Server 支持以下安全功能:
- 身份验证即服务 (AaaS)
- 跨多个应用程序类型的单一登录/注销 (SSO)
- API 的访问控制
- 联邦网关
Important
Duende Software 可能会要求你为 Duende Identity Server 的生产使用支付许可证费用。 有关详细信息,请参阅 从 .NET 5 中的 ASP.NET Core 迁移到 .NET 6。
有关详细信息,请参阅 Duende Identity Server 文档(Duende Software 网站)。
发布到 Azure
有关部署到 Azure 的信息,请参阅快速入门:部署 ASP.NET Web 应用。
其他资源
查看或下载本教程的示例代码。 请参阅如何下载。
有关详细信息,请参阅以下资源:
- 使用 ASP.NET Core 创建 Web API
- 教程:使用 ASP.NET Core 创建最小 API
- 带有 Swagger/OpenAPI 的 ASP.NET Core Web API 文档
- Razor ASP.NET Core 中带 Entity Framework Core 的页面 - 第 1 个教程(共 8 个)
- 在 ASP.NET Core 中路由到控制器操作
- ASP.NET Core Web API 中控制器操作的返回类型
- 将 ASP.NET Core 应用部署到 Azure 应用程序服务
- 托管和部署 ASP.NET Core
- 使用 ASP.NET Core 创建 Web API
 
              
             
              
            