C#开发后端:API 控制器(Controller)
要理解 API 控制器(Controller)的作用和代码构成,核心可以总结为:它是 “前端(小程序)和后端(服务 / 数据库)的中间桥梁”—— 接收前端请求、转发给服务层处理、返回结果给前端。
具体来说,整个流程的终点就是数据库:
- 前端收集用户输入的活动信息(如标题、时间、类型等);
- 前端通过接口调用(如 POST 请求)将信息发送给后端的
ActivityController; - 后端控制器(
ActivityController)中的CreateActivity方法接收数据,经过验证和处理(如自动填充AddTime、UpdateTime)后,通过数据库上下文(DbContext)将数据保存到数据库; - 最终,这些活动内容会被写入数据库中名为
sr_activity的表(对应实体类Sr_Activity的[Table("sr_activity")]配置),你可以在数据库中查询该表看到新增的活动记录
以下举例以微信小程序商城的banner和product举例
一、API 控制器(Controller)的核心作用
简单说,Controller 只做 “3 件事”,不承担复杂业务逻辑(复杂逻辑交给 Service 层):
- 接收请求:接收小程序发送的 HTTP 请求(比如 “获取 Banner 列表”“查询商品详情”),拿到请求参数(如商品 ID、分页页码)。
- 转发处理:把请求参数传给对应的 Service 层(比如
BannerService、ProductService),让 Service 去调用数据库或处理业务。 - 返回响应:接收 Service 层的处理结果,包装成小程序能识别的格式(如 JSON),返回给前端(成功则带数据,失败则带错误信息)。
二、Controller 里一般要写的代码(完整结构 + 示例)
代码结构分为 5 个核心部分,每部分都有明确用途:
1. 基础结构(必须有)
- 命名空间:对应项目目录(如 项目
.Controllers)。 - 继承
ControllerBase:ASP.NET Core 提供的 API 控制器基类,包含返回响应的方法(如Ok()、BadRequest())。 - 特性标签:
[ApiController]:自动启用参数校验、JSON 格式返回等 API 专属功能。[Route("api/[controller]")]:定义接口的基础 URL(如BannerController对应api/banner,ProductController对应api/product)。
// 1. 基础结构:命名空间+继承+特性
using Microsoft.AspNetCore.Mvc;
using Pgy_Wx.Services; // 引用服务层
using Pgy_Wx.Entities.Dto; // 引用请求/响应的DTO(数据传输对象)namespace Pgy_Wx.Controllers
{[ApiController] // 标记为API控制器[Route("api/[controller]")] // 基础URL:api/bannerpublic class BannerController : ControllerBase // 继承ControllerBase{// 2. 依赖注入:注入对应的Service(核心,避免Controller直接操作数据库)private readonly IBannerService _bannerService;// 构造函数:通过依赖注入获取Service实例public BannerController(IBannerService bannerService){_bannerService = bannerService;}// 3. Action方法:具体的API接口(每个方法对应一个前端请求)// 示例1:小程序端获取Banner列表(GET请求)[HttpGet("list")] // 完整URL:api/banner/list(GET请求)public async Task<IActionResult> GetBannerList(){// 步骤1:调用Service层获取数据(Controller不做复杂逻辑)var bannerList = await _bannerService.GetActiveBannerAsync();// 步骤2:包装响应返回给前端(JSON格式,带状态码)return Ok(new { code = 200, // 自定义业务状态码(方便前端判断)message = "获取成功", data = bannerList // 实际返回的数据});}// 示例2:后台管理端新增Banner(POST请求,需授权)[HttpPost("add")] // 完整URL:api/banner/add(POST请求)[Authorize(Roles = "Admin")] // 只有管理员能调用(需配置JWT授权)public async Task<IActionResult> AddBanner([FromBody] BannerAddDto dto){// 步骤1:参数校验(简单校验放Controller,复杂校验放Service)if (string.IsNullOrEmpty(dto.ImageUrl)){return BadRequest(new { code = 400, message = "Banner图片地址不能为空" });}if (string.IsNullOrEmpty(dto.JumpUrl)){return BadRequest(new { code = 400, message = "Banner跳转地址不能为空" });}// 步骤2:调用Service层执行新增逻辑await _bannerService.AddBannerAsync(dto);// 步骤3:返回成功响应return Ok(new { code = 200, message = "Banner新增成功" });}// 示例3:根据ID删除Banner(DELETE请求)[HttpDelete("delete/{id}")] // 完整URL:api/banner/delete/1(1是Banner的ID)[Authorize(Roles = "Admin")]public async Task<IActionResult> DeleteBanner(int id){// 步骤1:调用Service判断Banner是否存在var exists = await _bannerService.CheckBannerExistsAsync(id);if (!exists){return NotFound(new { code = 404, message = "Banner不存在" });}// 步骤2:调用Service执行删除await _bannerService.DeleteBannerAsync(id);// 步骤3:返回响应return Ok(new { code = 200, message = "Banner删除成功" });}}
}
2. 核心代码模块拆解
结合上面的示例,Controller 里的代码主要包含以下 4 类核心内容:
| 代码模块 | 作用 | 关键细节 |
|---|---|---|
| 依赖注入 | 注入 Service 层实例,让 Controller 能调用业务逻辑(解耦,不直接操作数据库) | 必须通过构造函数注入(如 private readonly IBannerService _bannerService),不能自己 new Service。 |
| Action 方法 | 对应具体的 API 接口,每个方法处理一种前端请求 | 用 [HttpGet]/[HttpPost]/[HttpPut]/[HttpDelete] 标记请求类型,括号里写接口的子路径(如 [HttpGet("list")])。 |
| 参数处理 | 接收前端传递的参数(如商品 ID、分页参数、新增数据) | 常用参数来源:- [FromQuery]:URL 参数(如 api/product/list?page=1&size=10)- [FromBody]:请求体(POST/PUT 传递 JSON 数据,如新增 Banner 的图片 / 跳转地址)- [FromRoute]:URL 路径参数(如 api/banner/delete/{id} 中的 id) |
| 响应返回 | 把处理结果包装成前端能识别的格式返回 | 用 ControllerBase 提供的方法:- Ok():成功(HTTP 200)- BadRequest():参数错误(HTTP 400)- NotFound():资源不存在(HTTP 404)- Unauthorized():未授权(HTTP 401)返回内容建议包含 code(业务状态码)、message(提示信息)、data(数据),方便前端统一处理。 |
三、Controller 的核心原则(避坑重点)
- 不写复杂业务逻辑:Controller 只做 “转发” 和 “简单校验”,比如参数是否为空、ID 是否为正数;复杂逻辑(如 Banner 排序、商品库存判断)必须放在 Service 层。
- 参数校验要做:简单的参数合法性校验(如必填项、格式)放在 Controller,避免无效请求传到 Service 层,提高效率。
- 响应格式统一:所有接口返回相同结构(如
{code, message, data}),让小程序前端能写统一的响应处理逻辑(不用每次判断返回格式)。 - 依赖注入解耦:绝对不要在 Controller 里
new IBannerService(),必须通过构造函数注入 —— 这样方便后期替换 Service 实现、写单元测试。
四、再举一个 ProductController 的关键接口示例(加深理解)
[ApiController]
[Route("api/[controller]")] // 基础URL:api/product
public class ProductController : ControllerBase
{private readonly IProductService _productService;public ProductController(IProductService productService){_productService = productService;}// 小程序端:获取商品详情(URL路径参数:api/product/detail/123)[HttpGet("detail/{id}")]public async Task<IActionResult> GetProductDetail(int id){// 1. 参数校验(ID不能小于1)if (id < 1){return BadRequest(new { code = 400, message = "商品ID无效" });}// 2. 调用Service获取商品详情(含关联的SKU、图片、品牌)var productDetail = await _productService.GetProductDetailAsync(id);if (productDetail == null){return NotFound(new { code = 404, message = "商品不存在或已下架" });}// 3. 返回结果return Ok(new { code = 200, message = "获取成功", data = productDetail });}// 小程序端:商品列表(带分页和分类筛选,URL参数:api/product/list?categoryId=2&page=1&size=10)[HttpGet("list")]public async Task<IActionResult> GetProductList([FromQuery] int categoryId = 0, // 分类ID(0表示全部)[FromQuery] int page = 1, // 页码(默认1)[FromQuery] int size = 10 // 每页数量(默认10)){// 1. 参数校验(分页参数不能小于1)if (page < 1) page = 1;if (size < 1 || size > 50) size = 10; // 限制最大每页50条// 2. 调用Service获取分页列表var (productList, totalCount) = await _productService.GetProductPageAsync(categoryId, page, size);// 3. 返回结果(带总页数,方便前端做分页控件)return Ok(new { code = 200, message = "获取成功", data = new { list = productList, totalCount = totalCount, totalPage = (int)Math.Ceiling((double)totalCount / size) } });}
}
总结
- Controller 是干嘛的:前端请求的 “接收站” 和 “中转站”,连接小程序和后端服务,不做复杂业务。
- 里面要写什么:基础结构(命名空间 + 特性)、依赖注入(Service)、Action 方法(接口)、参数处理、统一响应。
- 关键原则:不堆业务逻辑、参数要校验、响应要统一、依赖要注入。
