代码改变世界

C#开发后端:API 控制器(Controller) - 教程

2025-11-28 13:15  tlnshuju  阅读(0)  评论(0)    收藏  举报

要理解 API 控制器(Controller)的作用和代码构成,核心可以总结为:它是 “前端(小程序)和后端(服务 / 数据库)的中间桥梁”—— 接收前端请求、转发给服务层处理、返回结果给前端

具体来说,整个流程的终点就是数据库:

  1. 前端收集用户输入的活动信息(如标题、时间、类型等);
  2. 前端通过接口调用(如 POST 请求)将信息发送给后端的ActivityController
  3. 后端控制器(ActivityController)中的CreateActivity方法接收数据,经过验证和处理(如自动填充AddTimeUpdateTime)后,通过数据库上下文(DbContext)将数据保存到数据库;
  4. 最终,这些活动内容会被写入数据库中名为sr_activity的表(对应实体类Sr_Activity[Table("sr_activity")]配置),你可以在数据库中查询该表看到新增的活动记录

以下举例以微信小程序商城的banner和product举例

一、API 控制器(Controller)的核心作用

简单说,Controller 只做 “3 件事”,不承担复杂业务逻辑(复杂逻辑交给 Service 层):

  1. 接收请求:接收小程序发送的 HTTP 请求(比如 “获取 Banner 列表”“查询商品详情”),拿到请求参数(如商品 ID、分页页码)。
  2. 转发处理:把请求参数传给对应的 Service 层(比如 BannerServiceProductService),让 Service 去调用数据库或处理业务。
  3. 返回响应:接收 Service 层的处理结果,包装成小程序能识别的格式(如 JSON),返回给前端(成功则带数据,失败则带错误信息)。

二、Controller 里一般要写的代码(完整结构 + 示例)

代码结构分为 5 个核心部分,每部分都有明确用途:

1. 基础结构(必须有)
  • 命名空间:对应项目目录(如 项目.Controllers)。
  • 继承 ControllerBaseASP.NET Core 提供的 API 控制器基类,包含返回响应的方法(如 Ok()BadRequest())。
  • 特性标签:
    • [ApiController]:自动启用参数校验、JSON 格式返回等 API 专属功能。
    • [Route("api/[controller]")]:定义接口的基础 URL(如 BannerController 对应 api/bannerProductController 对应 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/banner
    public 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 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 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 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 的核心原则(避坑重点)

  1. 不写复杂业务逻辑:Controller 只做 “转发” 和 “简单校验”,比如参数是否为空、ID 是否为正数;复杂逻辑(如 Banner 排序、商品库存判断)必须放在 Service 层。
  2. 参数校验要做:简单的参数合法性校验(如必填项、格式)放在 Controller,避免无效请求传到 Service 层,提高效率。
  3. 响应格式统一:所有接口返回相同结构(如 {code, message, data}),让小程序前端能写统一的响应处理逻辑(不用每次判断返回格式)。
  4. 依赖注入解耦:绝对不要在 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 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 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 方法(接口)、参数处理、统一响应。
  • 关键原则:不堆业务逻辑、参数要校验、响应要统一、依赖要注入。