C# WebAPI通用响应设计

一、设计概述

本设计用于统一 Web API 的返回结构,解决各控制器返回格式不一致、前端解析困难的问题。

  • ApiResponse / ApiResponse<T>:统一的响应包装类(record),携带业务状态码、消息、数据、总数与时间戳。
  • ApiControllerBase:API 控制器基类,提供 SuccessError 等开箱即用的响应方法。业务控制器只需继承它,即可返回统一结构的 JSON。

设计约定:HTTP 状态码始终为 200,业务结果通过响应体中的 code 判断。这样便于前端统一拦截处理。

二、文件结构

通用响应设计/
├── ApiResponse.cs          // ResultType 枚举 + ApiResponse(非泛型) + ApiResponse<T>(泛型)
├── ApiControllerBase.cs    // API 控制器基类(统一响应方法)
├── ExampleController.cs    // 示例控制器(演示继承与调用)

2.1 ApiResponse

using System;

namespace PMS.Models.BaseCommDto
{
	/// <summary>
	/// 业务响应状态码
	/// </summary>
	public enum ResultTypeCode
	{
		/// <summary>成功</summary>
		Success = 200,

		/// <summary>请求参数错误</summary>
		BadRequest = 400,

		/// <summary>未授权(未登录或登录已过期)</summary>
		Unauthorized = 401,

		/// <summary>无权限(已登录但无权访问)</summary>
		Forbidden = 403,

		/// <summary>资源不存在</summary>
		NotFound = 404,

		/// <summary>参数校验失败</summary>
		ValidationError = 422,

		/// <summary>服务器内部错误</summary>
		ServerError = 500,

		/// <summary>业务失败(通用业务异常)</summary>
		BusinessError = 600
	}

	/// <summary>
	/// 通用通信响应包装器(非泛型版本,用于无数据负载场景)
	/// </summary>
	public record ApiResponse
	{
		/// <summary>
		/// 业务状态码(200 表示成功,其他为具体错误码)
		/// </summary>
		public int Code { get; init; }

		/// <summary>
		/// 响应消息(成功提示或错误详情)
		/// </summary>
		public string Message { get; init; } = string.Empty;

		/// <summary>
		/// 数据负载
		/// </summary>
		public object? Data { get; init; }

		/// <summary>
		/// 总记录数(分页场景使用)
		/// </summary>
		public int? Count { get; init; }

		/// <summary>
		/// 时间戳(服务器响应时间)
		/// </summary>
		public DateTime Timestamp { get; init; } = DateTime.Now;

		// ---------- 工厂方法 ----------

		/// <summary>
		/// 创建成功响应
		/// </summary>
		/// <param name="message">成功提示消息</param>
		/// <param name="data">数据负载</param>
		/// <param name="count">总记录数(分页场景)</param>
		public static ApiResponse Success(string message = "操作成功!", object? data = null, int? count = null)
			=> new() { Code = (int)ResultTypeCode.Success, Message = message, Data = data, Count = count };

		/// <summary>
		/// 创建失败响应
		/// </summary>
		/// <param name="message">错误提示消息</param>
		/// <param name="code">业务状态码</param>
		/// <param name="data">数据负载</param>
		/// <param name="count">总记录数</param>
		public static ApiResponse Error(string message = "操作失败!", int code = (int)ResultTypeCode.BusinessError, object? data = null, int? count = null)
			=> new() { Code = code, Message = message, Data = data, Count = count };

		/// <summary>创建请求参数错误响应</summary>
		public static ApiResponse BadRequest(string message = "请求参数错误!")
			=> Error(message, (int)ResultTypeCode.BadRequest);

		/// <summary>创建未授权响应</summary>
		public static ApiResponse Unauthorized(string message = "未授权,请先登录!")
			=> Error(message, (int)ResultTypeCode.Unauthorized);

		/// <summary>创建无权限访问响应</summary>
		public static ApiResponse Forbidden(string message = "无权限访问!")
			=> Error(message, (int)ResultTypeCode.Forbidden);

		/// <summary>创建资源不存在响应</summary>
		public static ApiResponse NotFound(string message = "请求的资源不存在!")
			=> Error(message, (int)ResultTypeCode.NotFound);
	}

	/// <summary>
	/// 通用通信响应包装器(泛型版本,用于携带强类型数据负载)
	/// </summary>
	/// <typeparam name="T">数据负载类型</typeparam>
	public record ApiResponse<T>
	{
		/// <summary>
		/// 业务状态码(200 表示成功,其他为具体错误码)
		/// </summary>
		public int Code { get; init; }

		/// <summary>
		/// 响应消息(成功提示或错误详情)
		/// </summary>
		public string Message { get; init; } = string.Empty;

		/// <summary>
		/// 数据负载
		/// </summary>
		public T? Data { get; init; }

		/// <summary>
		/// 总记录数(分页场景使用)
		/// </summary>
		public int? Count { get; init; }

		/// <summary>
		/// 时间戳(服务器响应时间)
		/// </summary>
		public DateTime Timestamp { get; init; } = DateTime.Now;

		// ---------- 工厂方法 ----------

		/// <summary>
		/// 创建成功响应(带数据)
		/// </summary>
		/// <param name="data">数据负载</param>
		/// <param name="message">成功提示消息</param>
		/// <param name="count">总记录数(分页场景)</param>
		public static ApiResponse<T> Success(T data, string message = "操作成功!", int? count = null)
			=> new() { Code = (int)ResultTypeCode.Success, Message = message, Data = data, Count = count };

		/// <summary>
		/// 创建失败响应
		/// </summary>
		/// <param name="message">错误提示消息</param>
		/// <param name="code">业务状态码</param>
		/// <param name="data">数据负载</param>
		/// <param name="count">总记录数</param>
		public static ApiResponse<T> Error(string message = "操作失败!", int code = (int)ResultTypeCode.BusinessError, T? data = default, int? count = null)
			=> new() { Code = code, Message = message, Data = data, Count = count };

		/// <summary>创建请求参数错误响应</summary>
		public static ApiResponse<T> BadRequest(string message = "请求参数错误!")
			=> Error(message, (int)ResultTypeCode.BadRequest);

		/// <summary>创建未授权响应</summary>
		public static ApiResponse<T> Unauthorized(string message = "未授权,请先登录!")
			=> Error(message, (int)ResultTypeCode.Unauthorized);

		/// <summary>创建无权限访问响应</summary>
		public static ApiResponse<T> Forbidden(string message = "无权限访问!")
			=> Error(message, (int)ResultTypeCode.Forbidden);

		/// <summary>创建资源不存在响应</summary>
		public static ApiResponse<T> NotFound(string message = "请求的资源不存在!")
			=> Error(message, (int)ResultTypeCode.NotFound);
	}
}

2.2 ApiControllerBase.cs 控制器基类

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using PMS.Models.BaseCommDto;

namespace PMS.Web.Controllers.Base
{
	/// <summary>
	/// API 控制器基类
	/// 所有业务控制器继承此类,统一使用 ApiResponse 响应格式,
	/// 调用 Success / Error 等方法即可返回统一结构的 JSON 数据。
	/// </summary>
	[ApiController]
	public class ApiControllerBase : ControllerBase
	{
		#region 成功响应

		/// <summary>
		/// 返回成功响应(无数据负载)
		/// </summary>
		/// <param name="message">成功提示消息</param>
		/// <returns>统一格式的成功 JSON</returns>
		protected IActionResult Success(string message = "操作成功!")
			=> Ok(ApiResponse.Success(message));

		/// <summary>
		/// 返回成功响应(带数据负载)
		/// </summary>
		/// <typeparam name="T">数据类型</typeparam>
		/// <param name="data">数据负载</param>
		/// <param name="message">成功提示消息</param>
		/// <param name="count">总记录数(分页场景)</param>
		/// <returns>统一格式的成功 JSON</returns>
		protected IActionResult Success<T>(T data, string message = "操作成功!", int? count = null)
			=> Ok(ApiResponse<T>.Success(data, message, count));

		/// <summary>
		/// 返回成功响应(分页列表场景)
		/// </summary>
		/// <typeparam name="T">列表元素类型</typeparam>
		/// <param name="items">当前页数据集合</param>
		/// <param name="total">总记录数</param>
		/// <param name="message">成功提示消息</param>
		/// <returns>统一格式的成功 JSON(data 为列表,count 为总数)</returns>
		protected IActionResult SuccessPage<T>(IEnumerable<T> items, int total, string message = "操作成功!")
			=> Ok(ApiResponse<IEnumerable<T>>.Success(items, message, total));

		#endregion

		#region 失败响应

		/// <summary>
		/// 返回失败响应(无数据负载)
		/// </summary>
		/// <param name="message">错误提示消息</param>
		/// <param name="code">业务状态码,默认 BusinessError(600)</param>
		/// <returns>统一格式的错误 JSON</returns>
		protected IActionResult Error(string message = "操作失败!", ResultTypeCode code = ResultTypeCode.BusinessError)
			=> Ok(ApiResponse.Error(message, (int)code));

		/// <summary>
		/// 返回失败响应(带数据负载)
		/// </summary>
		/// <typeparam name="T">数据类型</typeparam>
		/// <param name="message">错误提示消息</param>
		/// <param name="data">数据负载</param>
		/// <param name="code">业务状态码,默认 BusinessError(600)</param>
		/// <returns>统一格式的错误 JSON</returns>
		protected IActionResult Error<T>(string message, T? data = default, ResultTypeCode code = ResultTypeCode.BusinessError)
			=> Ok(ApiResponse<T>.Error(message, (int)code, data));

		#endregion

		#region 常见错误场景

		/// <summary>返回请求参数错误响应</summary>
		protected IActionResult BadRequestResult(string message = "请求参数错误!")
			=> Ok(ApiResponse.BadRequest(message));

		/// <summary>返回未授权响应(未登录或登录已过期)</summary>
		protected IActionResult UnauthorizedResult(string message = "未授权,请先登录!")
			=> Ok(ApiResponse.Unauthorized(message));

		/// <summary>返回无权限访问响应(已登录但无权访问)</summary>
		protected IActionResult ForbiddenResult(string message = "无权限访问!")
			=> Ok(ApiResponse.Forbidden(message));

		/// <summary>返回资源不存在响应</summary>
		protected IActionResult NotFoundResult(string message = "请求的资源不存在!")
			=> Ok(ApiResponse.NotFound(message));

		/// <summary>返回服务器内部错误响应</summary>
		protected IActionResult ServerErrorResult(string message = "服务器内部错误!")
			=> Ok(ApiResponse.Error(message, (int)ResultTypeCode.ServerError));

		#endregion
	}
}

三、响应格式定义

3.1 ApiResponse(非泛型,无强类型数据)

字段类型说明
Codeint业务状态码,200 表示成功
Messagestring提示消息
Dataobject?数据负载
Countint?总记录数(分页)
TimestampDateTime服务器响应时间

3.2 ApiResponse<T>(泛型,携带强类型数据)

字段同上,Data 类型为 T?,序列化时保持强类型结构。

3.3 JSON 输出示例

成功·带数据:

{
  "code": 200,
  "message": "查询成功",
  "data": { "id": 1, "name": "张三", "email": "zhangsan@example.com" },
  "count": null,
  "timestamp": "2026-08-20T10:30:00+08:00"
}

成功·分页列表:

{
  "code": 200,
  "message": "查询成功",
  "data": [
    { "id": 1, "name": "张三" },
    { "id": 2, "name": "李四" }
  ],
  "count": 3,
  "timestamp": "2026-08-20T10:30:00+08:00"
}

失败·无数据:

{
  "code": 404,
  "message": "未找到 ID 为 5 的用户",
  "data": null,
  "count": null,
  "timestamp": "2026-08-20T10:30:00+08:00"
}

四、业务状态码(ResultType)

枚举值数值含义
Success200成功
BadRequest400请求参数错误
Unauthorized401未授权(未登录或登录已过期)
Forbidden403无权限(已登录但无权访问)
NotFound404资源不存在
ValidationError422参数校验失败
ServerError500服务器内部错误
BusinessError600业务失败(通用)

五、ApiControllerBase 基类方法

方法参数说明返回 Code
Success(message)message 默认"操作成功!"成功·无数据200
Success<T>(data, message, count?)data 数据;message 提示;count 总数成功·带数据200
SuccessPage<T>(items, total, message)items 当前页集合;total 总数成功·分页列表200
Error(message, code)message 默认"操作失败!";code 默认 BusinessError失败·无数据默认 600
Error<T>(message, data, code)data 数据负载失败·带数据默认 600
BadRequestResult(message)请求参数错误参数错误400
UnauthorizedResult(message)未授权未登录/过期401
ForbiddenResult(message)无权限禁止访问403
NotFoundResult(message)资源不存在未找到404
ServerErrorResult(message)服务器内部错误服务异常500

说明:Error("提示", ResultType.NotFound) 也可用枚举指定任意业务状态码,方法内部自动转为 int。

六、快速上手

6.1 注册服务(Program.cs)

builder.Services.AddControllers();

[ApiController] 与路由特性已标注在基类上,子类自动继承。

6.2 控制器继承基类

using Microsoft.AspNetCore.Mvc;
using PMS.Web.Controllers.Base;

[Route("api/[controller]")]
public class UserController : ApiControllerBase
{
    // 无数据成功
    [HttpPost("logout")]
    public IActionResult Logout()
        => Success("退出登录成功");

    // 带数据成功
    [HttpGet("{id:int}")]
    public IActionResult Get(int id)
    {
        var user = _service.GetById(id);
        if (user is null)
            return NotFoundResult($"未找到 ID 为 {id} 的用户");
        return Success(user, "查询成功");
    }

    // 分页成功
    [HttpGet("list")]
    public IActionResult List(int pageIndex = 1, int pageSize = 10)
    {
        var (items, total) = _service.GetPage(pageIndex, pageSize);
        return SuccessPage(items, total, "查询成功");
    }

    // 失败响应
    [HttpPost]
    public IActionResult Create(UserDto dto)
    {
        if (string.IsNullOrWhiteSpace(dto.Name))
            return Error("用户名不能为空", code: ResultType.ValidationError);
        // ...
        return Success(dto, "新增成功");
    }
}

6.3 异步控制器

[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id)
{
    var user = await _service.GetByIdAsync(id);
    if (user is null)
        return NotFoundResult($"未找到 ID 为 {id} 的用户");
    await _service.DeleteAsync(id);
    return Success($"用户 {user.Name} 已删除");
}

6.4 不使用基类时手动构造

return Ok(ApiResponse.Success("操作成功"));
return Ok(ApiResponse<User>.Success(user, "查询成功"));
return Ok(ApiResponse.Error("操作失败", (int)ResultType.NotFound));

七、注意事项

  1. HTTP 状态码固定为 200:统一走 Ok(...)。若某接口需要真正的 HTTP 非 200 状态码(如 401 供网关判断),可改用 StatusCode(401, ApiResponse.Unauthorized())
  2. JSON 命名策略:默认采用 System.Text.Json 的 camelCase(小驼峰)输出;如项目配置了其他命名策略,序列化结果随之变化。
  3. Success("文本")Success<T>(data) 重载:传字符串时命中"无数据"重载;传对象时命中"带数据"重载(泛型自动推断),二者不会歧义。
  4. 与前端约定:前端应统一判断 code === 200 视为成功,其余均为失败,并读取 message 提示。

八、扩展建议

  • 全局异常处理:可在中间件 / IExceptionFilter 中捕获未处理异常,统一返回 ServerErrorResult 格式,避免控制器内重复 try-catch。
  • 新增业务码:业务需要时在 ResultType 枚举中追加,如 NoStock = 601,并直接用于 Error(message, ResultType.NoStock)
  • 模型校验:配合 [ApiController] 自动 400 响应,可通过 ModelState 提取第一条错误信息,包装成 ValidationError 返回。

九、使用示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using PMS.Models.BaseCommDto;
using PMS.Web.Controllers.Base;

namespace PMS.Web.Controllers
{
	/// <summary>
	/// 示例控制器
	/// 演示如何继承 ApiControllerBase 并使用 Success / Error 等方法返回统一格式的响应。
	/// </summary>
	[Route("api/[controller]")]
	public class ExampleController : ApiControllerBase
	{
		private static readonly List<UserDto> Users = new()
		{
			new UserDto { Id = 1, Name = "张三", Email = "zhangsan@example.com" },
			new UserDto { Id = 2, Name = "李四", Email = "lisi@example.com" },
			new UserDto { Id = 3, Name = "王五", Email = "wangwu@example.com" }
		};

		/// <summary>
		/// 分页查询用户列表
		/// GET /api/Example/list?pageIndex=1&amp;pageSize=2
		/// </summary>
		[HttpGet("list")]
		public IActionResult GetList([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 10)
		{
			var items = Users.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
			return SuccessPage(items, Users.Count, "查询成功");
		}

		/// <summary>
		/// 根据 ID 查询用户
		/// GET /api/Example/1
		/// </summary>
		[HttpGet("{id:int}")]
		public IActionResult GetById(int id)
		{
			if (id <= 0)
				return BadRequestResult("无效的用户 ID");

			var user = Users.FirstOrDefault(u => u.Id == id);
			if (user is null)
				return NotFoundResult($"未找到 ID 为 {id} 的用户");

			return Success(user, "查询成功");
		}

		/// <summary>
		/// 新增用户
		/// POST /api/Example
		/// </summary>
		[HttpPost]
		public IActionResult Create([FromBody] UserDto dto)
		{
			if (string.IsNullOrWhiteSpace(dto.Name))
				return Error("用户名不能为空", code: ResultType.ValidationError);

			dto.Id = Users.Count == 0 ? 1 : Users.Max(u => u.Id) + 1;
			Users.Add(dto);
			return Success(dto, "新增成功");
		}

		/// <summary>
		/// 删除用户(异步示例)
		/// DELETE /api/Example/1
		/// </summary>
		[HttpDelete("{id:int}")]
		public async Task<IActionResult> Delete(int id)
		{
			var user = Users.FirstOrDefault(u => u.Id == id);
			if (user is null)
				return NotFoundResult($"未找到 ID 为 {id} 的用户");

			// 模拟异步业务操作
			await Task.Delay(10);

			Users.Remove(user);
			return Success($"用户 {user.Name} 已删除");
		}
	}

	/// <summary>示例数据模型</summary>
	public record UserDto
	{
		public int Id { get; set; }
		public string Name { get; set; } = string.Empty;
		public string? Email { get; set; }
	}
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐