← 返回首页目录
# 使用ASP.NET Core创建基于控制器的Web API完整教程

**作者:吉祥法师**

## 教程概述

本教程旨在教授如何使用ASP.NET Core构建基于控制器的Web API应用程序,该API将集成数据库功能。在ASP.NET Core中,创建API的另一种方式是使用最小API(Minimal APIs)。开发者在选择两种方案时,可以参考微软官方提供的API概览文档进行决策。若需要了解最小API的创建方法,可查阅专门的最小API教程。

本教程将构建一个功能完整的待办事项(Todo)管理API,涵盖以下核心端点:

- **GET /api/todoitems**:获取所有待办事项,无需请求体,返回待办事项数组
- **GET /api/todoitems/{id}**:根据ID获取特定待办事项,无需请求体,返回单个待办事项
- **POST /api/todoitems**:添加新的待办事项,请求体为待办事项对象,返回新建的待办事项
- **PUT /api/todoitems/{id}**:更新现有待办事项,请求体为待办事项对象,无需响应体
- **DELETE /api/todoitems/{id}**:删除指定待办事项,无需请求体和响应体

## 环境准备与项目创建

### 所需开发环境

**Visual Studio 方案**:需要安装Visual Studio 2022,并确保包含"ASP.NET和Web开发"工作负载。

**Visual Studio Code方案**:需要安装VS Code、C# Dev Kit扩展以及.NET 9 SDK。该方案支持在macOS、Linux或Windows系统上操作,若使用其他集成开发环境可能需要相应调整。

### 创建Web API项目

**Visual Studio创建步骤**:
1. 从"文件"菜单选择"新建"→"项目"
2. 搜索"Web API",选择"ASP.NET Core Web API"模板
3. 将项目命名为"TodoApi"并点击"下一步"
4. 在附加信息对话框中,确认框架为.NET 9.0,勾选"启用OpenAPI支持"和"使用控制器"选项

**Visual Studio Code创建步骤**:
1. 打开集成终端,切换到项目存放目录
2. 执行以下命令序列:
   - `dotnet new webapi --use-controllers -o TodoApi`:创建新Web API项目
   - `cd TodoApi`:进入项目目录
   - `dotnet add package Microsoft.EntityFrameworkCore.InMemory`:添加内存数据库支持
   - `code -r .`:在当前VS Code实例中打开项目

### 添加NuGet包

项目需要添加Microsoft.EntityFrameworkCore.InMemory包来支持内存数据库。在Visual Studio中,可通过"工具"→"NuGet包管理器"→"管理解决方案的NuGet包"进行安装,搜索并选择对应包后点击安装即可。

## 项目运行与初始验证

### 运行项目

**Visual Studio**:按Ctrl+F5启动应用。首次运行可能需要信任IIS Express SSL证书和开发证书,依次点击"是"确认即可。应用启动后,终端窗口会显示监听地址,如https://localhost:7260。

**Visual Studio Code**:先运行`dotnet dev-certs https --trust`信任HTTPS开发证书,然后执行`dotnet run --launch-profile https`启动应用。

### 验证API

由于初始项目没有根端点,直接访问https://localhost:端口会返回404错误。在URL后追加`/weatherforecast`即可测试预置的WeatherForecast API,浏览器会返回JSON格式的天气数据,包含日期、温度(摄氏和华氏)及天气摘要等信息。

## 添加Swagger测试工具

为提高API测试效率,本教程使用NSwag.AspNetCore包集成Swagger工具:

### 安装和配置

1. 执行`dotnet add package NSwag.AspNetCore`添加包
2. 在Program.cs中配置Swagger中间件,仅在开发环境中启用:

```csharp
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.UseSwaggerUi(options => {
        options.DocumentPath = "/openapi/v1.json";
    });
}
```

注意:Swagger仅在开发环境启用,因为生产环境暴露API结构可能带来安全风险。

### 查看OpenAPI规范

运行项目后,访问https://localhost:端口/openapi/v1.json可以查看描述API端点、请求/响应格式、参数等信息的JSON格式OpenAPI规范文档。

## 创建数据模型

### 添加TodoItem模型类

在项目中创建Models文件夹,添加TodoItem.cs文件:

```csharp
namespace TodoApi.Models;

public class TodoItem
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}
```

Id属性在关系数据库中作为唯一键使用。模型类虽可放在项目任意位置,但按惯例放置在Models文件夹中。

## 配置数据库上下文

### 创建TodoContext类

在Models文件夹中添加TodoContext.cs:

```csharp
using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models;

public class TodoContext : DbContext
{
    public TodoContext(DbContextOptions options)
        : base(options)
    {
    }

    public DbSet TodoItems { get; set; } = null!;
}
```

该数据库上下文类派生自DbContext,是协调Entity Framework功能的核心类。

### 注册数据库上下文

在Program.cs中注册服务,指定使用内存数据库:

```csharp
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddDbContext(opt =>
    opt.UseInMemoryDatabase("TodoList"));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.UseSwaggerUi(options => {
        options.DocumentPath = "/openapi/v1.json";
    });
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
```

这段代码完成了数据库上下文的依赖注入注册,并指定使用名为"TodoList"的内存数据库。

## 生成API控制器

### Visual Studio方案

1. 右键单击Controllers文件夹,选择"添加"→"新建脚手架项"
2. 选择"使用Entity Framework的API控制器"
3. 在对话框中,模型类选择TodoItem,数据上下文类选择TodoContext
4. 点击"添加"完成生成

### VS Code方案

执行以下命令序列安装脚手架工具并生成控制器:

```bash
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet tool uninstall -g dotnet-aspnet-codegenerator
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet tool update -g dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator controller -name TodoItemsController -async -api -m TodoItem -dc TodoContext -outDir Controllers
```

生成的控制器具备以下特点:
- 标记[ApiController]属性,表明该控制器响应Web API请求
- 通过依赖注入获取数据库上下文,用于CRUD操作
- 路由模板不含[action]标记,这意味着方法名不参与路由匹配

## 实现CRUD操作

### POST方法优化

将PostTodoItem方法中的硬编码字符串替换为nameof操作符:

```csharp
[HttpPost]
public async Task> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetTodoItem), 
        new { id = todoItem.Id }, todoItem);
}
```

CreatedAtAction方法在成功时返回HTTP 201状态码,这是POST方法创建资源的标准响应,同时添加包含新资源URI的Location头。使用nameof关键字避免了硬编码方法名。

### GET方法解析

控制器包含两个GET端点:

```csharp
[HttpGet]
public async Task>> GetTodoItems()
{
    return await _context.TodoItems.ToListAsync();
}

[HttpGet("{id}")]
public async Task> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}
```

路由构造原则:URL路径以控制器Route属性中的模板字符串"api/[controller]"开头,[controller]被替换为控制器名(去掉Controller后缀)。对于带参数的路径,"{id}"是待办事项唯一标识的占位符,其值会传递给方法的id参数。

### 返回值类型说明

ActionResult类型允许返回多种HTTP状态码。ASP.NET Core会自动将对象序列化为JSON并写入响应体。无异常时返回200 OK,未处理异常转换为5xx错误。GetTodoItem方法可返回404(未找到匹配项)或200(成功返回JSON响应体)。

### PUT方法详解

```csharp
[HttpPut("{id}")]
public async Task 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();
}
```

PUT方法与POST类似,但使用HTTP PUT语义,响应为204 No Content。根据HTTP规范,PUT请求要求客户端发送完整的更新实体,如需部分更新应使用HTTP PATCH。

### DELETE方法实现

```csharp
[HttpDelete("{id}")]
public async Task DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}
```

## API测试实践

### 使用Endpoints Explorer测试

Visual Studio的Endpoints Explorer可以右键点击端点生成测试请求,自动创建.http文件。对于POST请求,需要提供JSON格式的请求体:

```json
{
  "name": "walk dog",
  "isComplete": true
}
```

发送请求后可在响应面板查看返回结果,包括201状态码和新建项的ID。

### 使用Swagger UI测试

运行应用后,访问https://localhost:端口/swagger进入测试页面。点击Try it out按钮,填充请求参数后执行。Swagger提供cURL命令示例、请求URL和服务器响应等详细信息,便于调试。

### 完整测试流程建议

1. 通过POST创建新待办事项(如"walk dog")
2. 使用GET验证创建结果
3. 通过PUT更新事项名称(如改为"feed fish"),验证204响应
4. 使用DELETE删除事项,确认204响应
5. 再次GET验证删除成功

注意:内存数据库每次应用重启后数据清空,需要重新POST数据。

## 安全考虑与最佳实践

### 防止过度发布(Over-Posting)

当前示例暴露了整个TodoItem对象,生产环境应用通常通过数据传输对象(DTO)限制输入和返回的数据子集,防止恶意用户提交额外字段修改不应更改的数据。这需要创建专门的请求/响应模型,并在控制器中进行映射。

### 其他测试工具

除Swagger外,开发者还可使用HTTP REPL、curl命令行工具或Fiddler等工具测试Web API。Swagger会显示其提交的curl命令,方便复制使用。

## 总结

本教程完整演示了使用ASP.NET Core创建控制器基础Web API的全过程,涵盖项目创建、依赖配置、数据模型定义、数据库上下文设置、控制器生成及CRUD操作实现。通过使用内存数据库和Swagger测试工具,开发者可以快速理解Web API的核心概念并投入实际开发。生产环境部署时需考虑数据库持久化、数据验证、安全性增强和API版本管理等扩展需求。