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。
查看或下载本教程的示例代码。 请参阅如何下载。
有关更多信息,请参见以下资源: