06-ASPNETCore-WebAPI开发

一、前言

从这篇文章开始,我们进入进阶开发部分。Web API 是现代应用开发的核心,无论是网站、手机 App 还是小程序,都需要通过 API 与后端服务器通信。

ASP.NET Core 是微软推出的高性能 Web 框架,用它来开发 Web API 既简单又高效。

二、什么是 Web API?

这一节先建立直观理解,再用 RESTful 的角度把概念落到设计规范上,方便你后续写接口时有统一的思路。

2.1 用大白话解释

这里用生活场景来理解 Web API 的角色。想象你去餐厅吃饭,你(客户端)看菜单点菜,服务员(API)把你的需求传达给厨房,厨房(服务器)做好菜后再由服务员端给你,整个过程里服务员负责接收请求、协调处理并返回结果,这就是 Web API 的本质。

2.2 RESTful API

REST 是一种 API 设计风格,核心是用 HTTP 方法表达资源操作,常见对应关系如下表所示。

HTTP 方法 用途 示例
GET 获取数据 获取用户列表
POST 创建数据 创建新用户
PUT 更新数据(全量) 更新用户信息
PATCH 更新数据(部分) 更新用户邮箱
DELETE 删除数据 删除用户

从语义上看,GET 只读取资源,POST 创建资源,PUT 用完整实体替换现有资源,PATCH 仅修改部分字段,DELETE 则删除资源,这样的约定能让 API 更可读、也更容易被工具和团队理解。

除了方法语义,RESTful 还强调"资源"的概念,比如用 /users 表示用户集合、/users/1 表示具体用户,这种命名方式天然可预测。配合清晰的状态码与一致的数据结构,前端与第三方调用方能更快理解接口行为,也更容易用自动化工具生成客户端代码与文档。

三、创建第一个 Web API 项目

3.1 使用命令行创建

本节用最短路径把项目跑起来,先感受开发流程,再深入结构与代码。

bash 复制代码
# 创建 Web API 项目
dotnet new webapi -n MyFirstApi

# 进入项目目录
cd MyFirstApi

# 运行项目
dotnet run

这三行命令依次完成了项目模板创建、进入目录和启动服务,其中 dotnet new webapi 会生成一个自带控制器与 Swagger 的基础 Web API 项目,dotnet run 会编译并启动 Kestrel 服务器。运行后打开浏览器访问 https://localhost:5001/swagger,你会看到 Swagger UI 界面,它会自动根据控制器生成接口文档并提供在线测试入口。

如果你在开发过程中修改了代码,dotnet run 会重新编译并重启应用,你也可以在实际项目中启用热重载以提升迭代速度。对于初学者来说,先把项目跑通、能通过 Swagger 发出请求并看到响应,是理解 Web API 开发流程的最佳起点。

3.2 项目结构

先认识目录与关键文件,后续讲解会频繁用到它们。

复制代码
MyFirstApi/
├── Controllers/           # 控制器目录
│   └── WeatherForecastController.cs
├── Properties/
│   └── launchSettings.json
├── appsettings.json       # 配置文件
├── appsettings.Development.json
├── Program.cs             # 程序入口
└── MyFirstApi.csproj      # 项目文件

在这个结构中,Controllers 用来放控制器类,Program.cs 是应用启动入口,appsettings.json 及其 Development 版本用于配置环境差异,launchSettings.json 提供本地启动时的地址与环境变量,而 csproj 则定义了项目编译信息与依赖。

后续你会经常在 Controllers 下添加业务接口,在 Models 或者更细的目录里放请求模型与实体模型,Program.cs 则承担"依赖注册与中间件编排"的职责。理解这几个核心位置,会让你更快定位问题和组织代码结构。

3.3 理解 Program.cs

这里把启动文件逐行拆开看,理解服务注册与中间件管道的关系。

csharp 复制代码
var builder = WebApplication.CreateBuilder(args);

// 添加服务到容器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 配置 HTTP 请求管道
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

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

app.Run();

这段代码可以理解为"先注册能力,再配置流程"。WebApplication.CreateBuilder(args) 创建应用构建器并读取配置,builder.Services.AddControllers() 把 MVC 控制器相关服务注册进依赖注入容器,AddEndpointsApiExplorer()AddSwaggerGen() 分别提供 API 描述与 Swagger 文档生成。builder.Build() 会把已注册的服务和配置编译成可运行的应用实例,之后通过 app.UseSwagger()app.UseSwaggerUI() 在开发环境中加入 Swagger 中间件,UseHttpsRedirection() 处理 HTTP 到 HTTPS 的跳转,UseAuthorization() 启用授权中间件,MapControllers() 将控制器路由挂接到请求管道上,最后 app.Run() 启动应用并开始监听请求。

需要注意的是,中间件的顺序会直接影响请求处理流程,比如授权中间件必须放在路由映射之前才能生效,异常处理与日志中间件通常也要放在更靠前的位置。理解"从上到下依次执行"的管道模型,有助于你在出现 401、404 或跨域问题时快速定位原因。

四、创建控制器

4.1 基本控制器结构

先用一个最小可用的控制器示例,展示路由与动作方法是如何对应到 HTTP 请求的。

csharp 复制代码
using Microsoft.AspNetCore.Mvc;

namespace MyFirstApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    // GET: api/users
    [HttpGet]
    public IActionResult GetAll()
    {
        return Ok(new[] { "张三", "李四", "王五" });
    }

    // GET: api/users/1
    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        return Ok(new { Id = id, Name = "张三" });
    }

    // POST: api/users
    [HttpPost]
    public IActionResult Create([FromBody] string name)
    {
        return CreatedAtAction(nameof(GetById), new { id = 1 }, new { Id = 1, Name = name });
    }

    // PUT: api/users/1
    [HttpPut("{id}")]
    public IActionResult Update(int id, [FromBody] string name)
    {
        return NoContent();
    }

    // DELETE: api/users/1
    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        return NoContent();
    }
}

这里的 [ApiController] 标记告诉框架启用 Web API 习惯用法,比如自动模型验证与更友好的参数绑定错误返回;[Route("api/[controller]")] 表示路由以 api/ 开头,[controller] 会被替换为类名去掉 Controller 后的部分。每个动作方法通过 [HttpGet][HttpPost] 等特性绑定到 HTTP 方法与路由模板,方法返回 IActionResult 以便灵活返回不同的 HTTP 状态码与结果体,例如 Ok() 返回 200,CreatedAtAction() 返回 201 并带 Location 头,NoContent() 返回 204 表示成功但不包含响应体。

控制器的职责是协调输入与输出,它不应该承载复杂的业务逻辑。实际项目中,你会把业务计算放到服务层,再由控制器调用服务并返回结果,这样结构更清晰,也便于测试与维护。

五、使用模型(DTO)

这一节把输入输出的结构抽象成模型类,这样控制器更清晰,数据也更容易校验与扩展。

csharp 复制代码
// Models/User.cs
public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public int Age { get; set; }
    public DateTime CreatedAt { get; set; }
}

// Models/CreateUserRequest.cs
public class CreateUserRequest
{
    public string Name { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public int Age { get; set; }
}

// Models/UpdateUserRequest.cs
public class UpdateUserRequest
{
    public string? Name { get; set; }
    public string? Email { get; set; }
    public int? Age { get; set; }
}

这里把"领域模型"和"请求模型"区分开:User 表示系统中的用户实体,包含 IdCreatedAt 等系统生成字段;CreateUserRequestUpdateUserRequest 则只包含来自客户端的输入字段,且更新模型使用可空类型表示"是否提供该字段"。这种分离能避免前端随意覆盖服务器生成的字段,同时让更新操作更安全。

当模型逐渐复杂时,你还可以进一步拆分为读取模型与返回模型,例如 UserResponse 专门面向前端展示,避免直接暴露内部字段。DTO 的核心价值是"让外部输入输出与内部结构隔离",这是大型系统里非常重要的边界。

5.1 完整的用户控制器

本节把模型应用到完整的 CRUD 流程中,让你看到请求、处理与响应的完整链路。

csharp 复制代码
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    // 模拟数据库
    private static List<User> _users = new()
    {
        new User { Id = 1, Name = "张三", Email = "zhangsan@example.com", Age = 25, CreatedAt = DateTime.Now },
        new User { Id = 2, Name = "李四", Email = "lisi@example.com", Age = 30, CreatedAt = DateTime.Now }
    };

    // GET: api/users
    [HttpGet]
    public ActionResult<IEnumerable<User>> GetAll()
    {
        return Ok(_users);
    }

    // GET: api/users/1
    [HttpGet("{id}")]
    public ActionResult<User> GetById(int id)
    {
        var user = _users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound(new { message = "用户不存在" });
        }
        return Ok(user);
    }

    // POST: api/users
    [HttpPost]
    public ActionResult<User> Create([FromBody] CreateUserRequest request)
    {
        var user = new User
        {
            Id = _users.Max(u => u.Id) + 1,
            Name = request.Name,
            Email = request.Email,
            Age = request.Age,
            CreatedAt = DateTime.Now
        };
        _users.Add(user);
        return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
    }

    // PUT: api/users/1
    [HttpPut("{id}")]
    public IActionResult Update(int id, [FromBody] UpdateUserRequest request)
    {
        var user = _users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound(new { message = "用户不存在" });
        }

        if (request.Name != null) user.Name = request.Name;
        if (request.Email != null) user.Email = request.Email;
        if (request.Age.HasValue) user.Age = request.Age.Value;

        return NoContent();
    }

    // DELETE: api/users/1
    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        var user = _users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound(new { message = "用户不存在" });
        }
        _users.Remove(user);
        return NoContent();
    }
}

这段代码通过一个静态集合模拟数据库,GetAll() 直接返回列表,GetById() 通过 FirstOrDefault 查找并在找不到时返回 404。Create() 会根据当前最大 Id 生成新用户并加入集合,随后使用 CreatedAtAction() 指向 GetById,让客户端能直接定位新资源。Update() 采用可空字段做"部分更新",只有传入的字段才会覆盖已有数据;Delete() 删除前同样先校验存在性,避免无意义操作。这一套结构就是典型的 RESTful CRUD 模式。

需要强调的是,这里用内存集合只是为了演示流程,真实项目会用数据库或缓存进行持久化,并通过服务层处理并发、校验与日志记录。即便如此,控制器层的接口设计方式依然一致,换掉数据源不会影响外部 API 行为。

六、路由配置

6.1 路由属性

本段展示路由模板与约束写法,帮助你表达更清晰的地址规则。

csharp 复制代码
[ApiController]
[Route("api/v1/[controller]")]  // api/v1/products
public class ProductsController : ControllerBase
{
    // GET: api/v1/products
    [HttpGet]
    public IActionResult GetAll() => Ok();

    // GET: api/v1/products/123
    [HttpGet("{id:int}")]  // 约束 id 必须是整数
    public IActionResult GetById(int id) => Ok();

    // GET: api/v1/products/search?keyword=手机
    [HttpGet("search")]
    public IActionResult Search([FromQuery] string keyword) => Ok();

    // GET: api/v1/products/category/electronics
    [HttpGet("category/{categoryName}")]
    public IActionResult GetByCategory(string categoryName) => Ok();
}

[Route("api/v1/[controller]")] 体现了版本号在路径中的写法,[controller] 仍然是控制器名占位符。[HttpGet("{id:int}")] 中的 :int 是路由约束,确保只有整数才会匹配这个动作方法,从而减少错误请求的进入。searchcategory/{categoryName} 则演示了"静态段 + 参数段"的组合,用于表达查询与分类等业务意图。

在版本控制上,路径版本是最直观的做法,后续如果要升级接口,可以通过 /api/v2 并行维护新旧版本。除此之外也可以使用请求头或查询参数做版本区分,但对初学者来说路径版本更易理解和调试。

6.2 参数绑定

这里说明常见参数来源的绑定方式,方便你按需选择数据入口。

csharp 复制代码
public class ProductsController : ControllerBase
{
    // 从路由获取参数
    [HttpGet("{id}")]
    public IActionResult GetById([FromRoute] int id) => Ok();

    // 从查询字符串获取参数
    [HttpGet]
    public IActionResult Search(
        [FromQuery] string? keyword,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 10) => Ok();

    // 从请求体获取参数
    [HttpPost]
    public IActionResult Create([FromBody] CreateProductRequest request) => Ok();

    // 从请求头获取参数
    [HttpGet("auth")]
    public IActionResult GetWithAuth([FromHeader(Name = "Authorization")] string token) => Ok();
}

[FromRoute] 明确参数来自路由模板,[FromQuery] 表示从查询字符串取值,同时也支持默认值以简化分页参数,[FromBody] 用于读取 JSON 请求体并反序列化为对象,[FromHeader] 则读取指定请求头。虽然在 [ApiController] 下很多绑定可以自动推断,但显式标注能让接口意图更清晰,也便于阅读与维护。

参数绑定还有一个常见的细节是"命名一致性",比如查询参数 pageSize 会自动绑定到方法参数 pageSize,而大小写不敏感。只要命名清晰,接口调用方就能一眼看懂参数用途,减少文档成本。

七、数据验证

7.1 使用数据注解

这一部分用最常见的数据注解说明如何把校验规则直接写在模型上。

csharp 复制代码
using System.ComponentModel.DataAnnotations;

public class CreateUserRequest
{
    [Required(ErrorMessage = "姓名不能为空")]
    [StringLength(50, MinimumLength = 2, ErrorMessage = "姓名长度必须在2-50之间")]
    public string Name { get; set; } = string.Empty;

    [Required(ErrorMessage = "邮箱不能为空")]
    [EmailAddress(ErrorMessage = "邮箱格式不正确")]
    public string Email { get; set; } = string.Empty;

    [Range(1, 150, ErrorMessage = "年龄必须在1-150之间")]
    public int Age { get; set; }

    [Phone(ErrorMessage = "手机号格式不正确")]
    public string? Phone { get; set; }

    [Url(ErrorMessage = "网址格式不正确")]
    public string? Website { get; set; }
}

[Required] 强制字段必填,[StringLength] 约束长度范围,[EmailAddress][Phone][Url] 分别验证格式,而 [Range] 则用于数值区间校验。把这些规则写在请求模型上,可以在控制器逻辑执行前就完成校验,从而保证业务代码接收到的是合法数据。

当校验规则变得复杂时,也可以使用自定义验证特性或 FluentValidation 这类库,表达跨字段或业务级校验逻辑。无论使用哪种方式,目标都是让错误尽早暴露,并且让客户端得到明确、可执行的错误提示。

7.2 控制器中的验证

这里展示如何在动作方法里处理验证结果,以便向客户端返回明确的错误信息。

csharp 复制代码
[HttpPost]
public IActionResult Create([FromBody] CreateUserRequest request)
{
    // ModelState 会自动验证
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // 处理业务逻辑...
    return Ok();
}

在启用 [ApiController] 后,框架会在模型绑定完成时自动验证,ModelState 里会包含失败原因。这里显式判断 ModelState.IsValid 并返回 BadRequest(ModelState),可以将所有验证错误按字段返回给客户端,方便前端逐项提示用户修正。

如果你希望在模型验证失败时统一返回格式,也可以通过自定义过滤器或配置 ApiBehaviorOptions 来替换默认行为,这样可以保持错误返回结构的一致性,降低前端处理复杂度。

八、返回结果

8.1 常用返回方法

本节把常见 HTTP 状态码的返回方式集中演示,便于你根据场景选择。

csharp 复制代码
public class ExampleController : ControllerBase
{
    [HttpGet("ok")]
    public IActionResult OkExample()
    {
        return Ok(new { message = "成功" });  // 200
    }

    [HttpGet("created")]
    public IActionResult CreatedExample()
    {
        return Created("/api/users/1", new { id = 1 });  // 201
    }

    [HttpGet("nocontent")]
    public IActionResult NoContentExample()
    {
        return NoContent();  // 204
    }

    [HttpGet("badrequest")]
    public IActionResult BadRequestExample()
    {
        return BadRequest(new { error = "参数错误" });  // 400
    }

    [HttpGet("unauthorized")]
    public IActionResult UnauthorizedExample()
    {
        return Unauthorized();  // 401
    }

    [HttpGet("forbidden")]
    public IActionResult ForbiddenExample()
    {
        return Forbid();  // 403
    }

    [HttpGet("notfound")]
    public IActionResult NotFoundExample()
    {
        return NotFound(new { error = "资源不存在" });  // 404
    }

    [HttpGet("error")]
    public IActionResult ErrorExample()
    {
        return StatusCode(500, new { error = "服务器内部错误" });  // 500
    }
}

Ok() 返回 200 并带数据体,Created() 返回 201 并指向新资源地址,NoContent() 表示操作成功但无需返回内容,BadRequest() 用于客户端参数错误,Unauthorized()Forbid() 分别对应未认证与无权限场景,NotFound() 处理资源不存在,StatusCode(500, ...) 则用于明确的服务器端异常响应。合理的状态码能显著提升 API 的可用性与可调试性。

实践中建议先确认"业务成功或失败",再选择最贴切的 HTTP 状态码,不要把所有错误都用 200 包起来。状态码配合统一响应格式,能让调用方更快定位问题来源。

8.2 统一响应格式

这一部分通过统一响应模型来规范返回结构,便于前后端约定与日志追踪。

csharp 复制代码
// Models/ApiResponse.cs
public class ApiResponse<T>
{
    public int Code { get; set; }
    public string Message { get; set; } = string.Empty;
    public T? Data { get; set; }
    public DateTime Timestamp { get; set; } = DateTime.Now;

    public static ApiResponse<T> Success(T data, string message = "操作成功")
    {
        return new ApiResponse<T> { Code = 200, Message = message, Data = data };
    }

    public static ApiResponse<T> Fail(string message, int code = 400)
    {
        return new ApiResponse<T> { Code = code, Message = message };
    }
}

// 使用
[HttpGet("{id}")]
public ActionResult<ApiResponse<User>> GetById(int id)
{
    var user = _users.FirstOrDefault(u => u.Id == id);
    if (user == null)
    {
        return NotFound(ApiResponse<User>.Fail("用户不存在", 404));
    }
    return Ok(ApiResponse<User>.Success(user));
}

ApiResponse<T> 约定了统一的响应格式,其中 CodeMessage 表示业务级状态,Data 携带实际数据,Timestamp 记录服务器时间。Success()Fail() 作为工厂方法减少重复代码,同时把"成功与失败"的结构固定下来。控制器使用 ApiResponse<User>.Success(user) 返回一致的数据包,客户端解析逻辑也会更稳定。

统一响应不意味着忽视 HTTP 状态码,通常建议同时使用合适的状态码,再用 Code 表达更细的业务状态,比如"库存不足"或"账号被锁定"。这样既符合 HTTP 语义,也方便前端按业务场景做更精细的提示。

九、实战案例:商品管理 API

这一节综合前面的知识点,用一个商品管理 API 把查询、创建、分页、统一返回与验证串联起来。

csharp 复制代码
// Models/Product.cs
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int Stock { get; set; }
    public string Category { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
}

// Controllers/ProductsController.cs
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private static List<Product> _products = new()
    {
        new Product { Id = 1, Name = "iPhone 15", Description = "苹果手机", Price = 6999, Stock = 100, Category = "手机", CreatedAt = DateTime.Now },
        new Product { Id = 2, Name = "MacBook Pro", Description = "苹果笔记本", Price = 14999, Stock = 50, Category = "电脑", CreatedAt = DateTime.Now }
    };

    // 获取商品列表(支持分页和筛选)
    [HttpGet]
    public ActionResult<ApiResponse<object>> GetAll(
        [FromQuery] string? category,
        [FromQuery] decimal? minPrice,
        [FromQuery] decimal? maxPrice,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 10)
    {
        var query = _products.AsQueryable();

        if (!string.IsNullOrEmpty(category))
            query = query.Where(p => p.Category == category);

        if (minPrice.HasValue)
            query = query.Where(p => p.Price >= minPrice.Value);

        if (maxPrice.HasValue)
            query = query.Where(p => p.Price <= maxPrice.Value);

        var total = query.Count();
        var items = query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToList();

        return Ok(ApiResponse<object>.Success(new
        {
            items,
            pagination = new
            {
                page,
                pageSize,
                total,
                totalPages = (int)Math.Ceiling(total / (double)pageSize)
            }
        }));
    }

    // 获取单个商品
    [HttpGet("{id}")]
    public ActionResult<ApiResponse<Product>> GetById(int id)
    {
        var product = _products.FirstOrDefault(p => p.Id == id);
        if (product == null)
            return NotFound(ApiResponse<Product>.Fail("商品不存在", 404));

        return Ok(ApiResponse<Product>.Success(product));
    }

    // 创建商品
    [HttpPost]
    public ActionResult<ApiResponse<Product>> Create([FromBody] CreateProductRequest request)
    {
        var product = new Product
        {
            Id = _products.Max(p => p.Id) + 1,
            Name = request.Name,
            Description = request.Description,
            Price = request.Price,
            Stock = request.Stock,
            Category = request.Category,
            CreatedAt = DateTime.Now
        };
        _products.Add(product);

        return CreatedAtAction(
            nameof(GetById),
            new { id = product.Id },
            ApiResponse<Product>.Success(product, "商品创建成功"));
    }

    // 更新库存
    [HttpPatch("{id}/stock")]
    public IActionResult UpdateStock(int id, [FromBody] UpdateStockRequest request)
    {
        var product = _products.FirstOrDefault(p => p.Id == id);
        if (product == null)
            return NotFound(ApiResponse<object>.Fail("商品不存在", 404));

        product.Stock += request.Quantity;  // 可以是正数(入库)或负数(出库)
        product.UpdatedAt = DateTime.Now;

        if (product.Stock < 0)
            return BadRequest(ApiResponse<object>.Fail("库存不足"));

        return Ok(ApiResponse<object>.Success(new { newStock = product.Stock }));
    }
}

public class CreateProductRequest
{
    [Required(ErrorMessage = "商品名称不能为空")]
    public string Name { get; set; } = string.Empty;

    public string Description { get; set; } = string.Empty;

    [Range(0.01, double.MaxValue, ErrorMessage = "价格必须大于0")]
    public decimal Price { get; set; }

    [Range(0, int.MaxValue, ErrorMessage = "库存不能为负数")]
    public int Stock { get; set; }

    [Required(ErrorMessage = "分类不能为空")]
    public string Category { get; set; } = string.Empty;
}

public class UpdateStockRequest
{
    public int Quantity { get; set; }
}

这一套示例包含了完整的数据模型与控制器逻辑。列表接口通过 AsQueryable() 构建查询,并按分类与价格区间筛选,再用 SkipTake 实现分页,同时返回分页元数据;创建接口将请求模型映射到实体并设置 CreatedAt,再用 CreatedAtAction 返回资源地址;库存更新接口使用 PATCH 表达"部分更新",将增减数量应用到库存并校验是否出现负数,最后统一用 ApiResponse 返回结果。这样组织后,接口的语义、错误处理与响应结构都保持一致。

如果你把这个示例继续扩展到真实项目,可以加入排序、关键词搜索、软删除、并发控制等能力,并把列表分页与筛选封装成可复用的查询对象。通过这样的演进路径,示例代码就能逐步长成可落地的业务模块。

十、小结

这篇文章围绕 ASP.NET Core Web API 的核心实践展开,从 RESTful 设计、项目创建、控制器与路由、参数绑定、数据验证到统一响应格式逐步串联,并通过商品管理案例把各环节贯通起来。

十一、下一篇预告

下一篇我们将学习 Entity Framework Core,这是 .NET 中最流行的 ORM 框架,能让你用 C# 代码操作数据库,而不用写 SQL。


练习题:尝试创建一个图书管理 API,覆盖增删改查流程,同时实现分页查询功能,并添加数据验证以确保图书名称不为空、价格大于 0。

相关推荐
Zach_yuan2 小时前
自定义协议:实现网络计算器
linux·服务器·开发语言·网络
岁杪杪2 小时前
关于运维:LINUX 零基础
运维·服务器·php
2501_930707782 小时前
使用 C# .NET 从 PowerPoint 演示文稿中提取背景图片
c#·powerpoint·.net
tianyuanwo2 小时前
企业级NTP客户端配置指南:基于内部NTP服务器的实践
运维·服务器·ntp客户端
Charlie_lll2 小时前
力扣解题-移动零
后端·算法·leetcode
初级代码游戏2 小时前
套路化编程 C# winform 自适应缩放布局
开发语言·c#·winform·自动布局·自动缩放
_别来无恙_3 小时前
TFTP的使用Linux
linux·服务器
gaize12133 小时前
Moltbot(Clawdbot) 专属轻量服务器
运维·服务器
Zaralike3 小时前
Linux 服务器网络不通排查 SOP(标准操作流程)
linux·服务器·网络