ApiControllerBase
ApiControllerBase 是 Aegis Web API 的基础控制器。可以把它理解成:帮你把 Controller 的返回格式统一起来。
它解决什么问题
有了它之后,Controller 不用再自己拼响应对象。平时最常用的也就是这几种情况:
- 返回单对象
- 返回列表
- 返回分页列表
- 返回失败消息
- 返回参数校验失败
- 把
ServiceResult<T>直接转换成标准响应 - 文件下载
响应类型怎么理解
平时先记这三种就够:
ApiResponseEntityResponse<T>EntitiesResponse<T>
ApiResponse
基础响应类型,仅包含公共字段:
| 字段 | 类型 | 说明 |
|---|---|---|
Code | int | 状态码,200 表示成功 |
MessageType | MessageType | 前端消息展示方式 |
Message | string | 提示消息 |
它本身不带 Result,只是公共父级类型。
EntityResponse<T>
单对象返回时使用,继承 ApiResponse,额外带一个字段:
Result → T(业务数据)
JSON 示例:
{
"code": 200,
"messageType": 40,
"message": "OK",
"result": { "id": 1, "name": "张三" }
}
EntitiesResponse<T>
列表或分页列表返回时使用,继承 ApiResponse,Result 内包含列表和分页信息:
Result.List → T(列表数据)
Result.Total → long(总条数)
Result.CurrentIndex → int(当前页码,为 0 时不输出到 JSON)
JSON 示例:
{
"code": 200,
"messageType": 40,
"message": "OK",
"result": {
"list": [{ "id": 1 }, { "id": 2 }],
"total": 100,
"currentIndex": 1
}
}
MessageType 枚举
MessageType 控制前端如何展示消息:
| 值 | 名称 | 含义 |
|---|---|---|
| 10 | Notice | 提醒 |
| 20 | Message | 消息框 |
| 30 | Confirm | 确认框 |
| 40 | Ignore | 忽略,前端不展示(默认值) |
| 100 | Fallback | 降级 |
大部分场景用 Ignore(不需要前端弹消息)或 Notice(需要前端弹出提示)就够了。
ResponseCode 枚举
Code 字段的常用取值:
| 值 | 名称 | 含义 |
|---|---|---|
| 200 | Success | 成功 |
| 201 | Tips | 成功,但带提示信息 |
| 401 | UnAuthenticate | 未认证 |
| 402 | TokenExpired | Token 已过期 |
| 405 | NotAllowed | 不允许执行 |
| 100 | Failed | 失败 |
| 105 | ParametersWrong | 参数验证失败 |
成功系列方法
SuccessResult() — 无数据成功
不携带业务数据,仅返回成功状态。
[HttpDelete("Delete")]
public async Task<ApiResponse> Delete(long id)
{
await _userService.DeleteAsync(id);
return SuccessResult();
}
返回:ApiResponse,Code=200,Message="OK"。
SuccessResult(entity) — 返回单对象
[HttpGet("GetUser")]
public async Task<ApiResponse> GetUser(long id)
{
var dto = await _userService.GetUserAsync(id);
return SuccessResult(dto);
}
返回:EntityResponse<T>,Code=200。
SuccessResult(message, entity, messageType) — 带消息的单对象
当你需要前端展示提示信息时使用:
return SuccessResult("保存成功", dto, MessageType.Notice);
SuccessListResult(list) — 返回列表
不带分页信息的列表:
[HttpGet("GetAllRoles")]
public async Task<ApiResponse> GetAllRoles()
{
var list = await _roleService.GetAllAsync();
return SuccessListResult(list);
}
SuccessListResult(list, current, total) — 返回分页列表
手动指定页码和总数:
[HttpGet("GetList")]
public async Task<ApiResponse> GetList([FromQuery] PageListRequest request)
{
var list = await _userService.GetUsersAsync(request);
return SuccessListResult(list, request.PageIndex, totalCount);
}
SuccessListResult(pagedResult) — 接收 PagedResult
如果 Service 层返回的是 PagedResult<T>,可以直接传入:
[HttpGet("GetPagedList")]
public async Task<ApiResponse> GetPagedList([FromQuery] PageListRequest request)
{
var pagedResult = await _userService.GetPagedUsersAsync(request);
return SuccessListResult(pagedResult);
}
SuccessListResult(message, list, messageType) — 带消息的列表
return SuccessListResult("查询成功,数据已更新", list, MessageType.Notice);
失败系列方法
FailedResult(message) — 简单失败
最常用的失败返回:
if (dto == null)
{
return FailedResult("未查询到相关数据");
}
默认 Code=100,MessageType=Notice。
FailedResult(messages) — 多条失败消息
参数校验有多条错误时使用,消息之间会以换行连接:
var errors = new[] { "姓名不能为空", "年龄超出范围" };
return FailedResult(errors);
FailedResult(code, message) — 自定义 Code 的失败
需要使用特定错误码时:
// 使用 int 数字 Code
return FailedResult(500, "系统内部错误");
FailedResult(ResponseCode, message) — 使用 ResponseCode 枚举
// 使用预定义枚举
return FailedResult(ResponseCode.NotAllowed, "当前用户无权执行此操作");
return FailedResult(ResponseCode.TokenExpired, "登录已过期,请重新登录");
FailedResult(message, entity) — 携带数据的失败
失败时同时返回部分数据,前端可据此做后续处理:
return FailedResult("部分数据导入失败,请检查标红行", failedRows);
InValidResult(message) — 参数验证失败
专门用于参数校验不通过的场景,Code=105:
if (request.Id <= 0)
{
return InValidResult("Id 必须大于 0");
}
通用方法
Result(code, message, entity, messageType) — 通用单实体响应
当你需要完全控制 Code 和消息类型时:
return Result(ResponseCode.Tips, "操作成功但存在警告", result, MessageType.Notice);
返回 EntityResponse<T>。
ListResult(code, message, entities, total, currentIndex, messageType) — 通用列表响应
完全控制列表响应的所有字段:
return ListResult(ResponseCode.Success, "查询成功", list, total: 100, currentIndex: 1);
返回 EntitiesResponse<T>。
ServiceResult 转换
CallResult(serviceResult) — 将 ServiceResult 转为响应
如果 Service 层使用了 ServiceResult<T>,Controller 里不需要手动判断成功/失败:
[HttpPost("Create")]
public async Task<ApiResponse> Create(CreateRequest request)
{
var result = await _userService.CreateAsync(request);
return CallResult(result);
}
CallResult 会根据 ServiceResult.IsSuccess 自动选择返回成功还是失败:
IsSuccess = true→ 调用SuccessResult(data, message, messageType)IsSuccess = false→ 调用FailedResult(message, messageType)
文件下载
FileResult(bytes, fileType, fileName) — 按文件类型下载
传入 FileType 枚举,自动识别 Content-Type:
[HttpGet("ExportExcel")]
public async Task<ActionResult> ExportExcel()
{
var bytes = await _reportService.GenerateExcelAsync();
return FileResult(bytes, FileType.xlsx, "报表.xlsx");
}
FileType 支持的类型:
| 枚举值 | 说明 |
|---|---|
xls / xlsx | Excel |
doc / docx | Word |
ppt / pptx | PowerPoint |
pdf | |
zip | ZIP 压缩包 |
FileResult(bytes, contentType, fileName) — 自定义 Content-Type
return FileResult(bytes, "application/octet-stream", "data.bin");
FileResult(stream, contentType, fileName) — 流式下载
适合大文件场景:
[HttpGet("DownloadTemplate")]
public ActionResult DownloadTemplate()
{
var stream = new FileStream(templatePath, FileMode.Open);
return FileResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "模板.xlsx");
}
方法速查表
| 场景 | 方法 | 返回类型 |
|---|---|---|
| 无数据成功 | SuccessResult() | ApiResponse |
| 返回单对象 | SuccessResult(entity) | EntityResponse<T> |
| 带消息的单对象 | SuccessResult(message, entity, messageType) | EntityResponse<T> |
| 返回列表 | SuccessListResult(list) | EntitiesResponse<T> |
| 返回分页列表 | SuccessListResult(list, current, total) | EntitiesResponse<T> |
| 接收 PagedResult | SuccessListResult(pagedResult) | EntitiesResponse<T> |
| 带消息的列表 | SuccessListResult(message, list, messageType) | EntitiesResponse<T> |
| 简单失败 | FailedResult(message) | ApiResponse |
| 多条失败消息 | FailedResult(messages) | ApiResponse |
| 自定义 Code 失败 | FailedResult(code, message) | ApiResponse |
| 枚举 Code 失败 | FailedResult(ResponseCode, message) | ApiResponse |
| 携带数据的失败 | FailedResult(message, entity) | ApiResponse |
| 参数验证失败 | InValidResult(message) | ApiResponse |
| ServiceResult 转换 | CallResult(serviceResult) | ApiResponse |
| 通用单实体 | Result(code, message, entity, messageType) | EntityResponse<T> |
| 通用列表 | ListResult(...) | EntitiesResponse<T> |
| 文件下载(枚举类型) | FileResult(bytes, FileType, fileName) | ActionResult |
| 文件下载(自定义类型) | FileResult(bytes, contentType, fileName) | ActionResult |
| 文件下载(流) | FileResult(stream, contentType, fileName) | ActionResult |
如何扩展 Response
当标准的三种响应类型(ApiResponse、EntityResponse<T>、EntitiesResponse<T>)不满足业务需求时,有以下几种扩展方式。
方式一:继承 ApiResponse 添加自定义字段
ApiResponse 是 record 类型,可以被继承。如果你的业务需要固定的响应结构但又需要额外字段,可以定义自己的响应类型。
using Aegis.Transfer.Responses;
// 扩展:带 TraceId 的响应
public record TraceableResponse : ApiResponse
{
public string TraceId { get; set; }
public object Result { get; set; }
}
在 Controller 中直接使用:
[HttpGet("GetWithTrace")]
public ApiResponse GetWithTrace(long id)
{
var dto = _userService.GetUser(id);
return new TraceableResponse
{
Code = 200,
Message = "OK",
TraceId = HttpContext.TraceIdentifier,
Result = dto
};
}
输出 JSON:
{
"code": 200,
"messageType": 0,
"message": "OK",
"traceId": "00-abc123-def456-01",
"result": { "id": 1, "name": "张三" }
}
方式二:直接构造 EntityResponse / EntitiesResponse
基类方法(SuccessResult、SuccessListResult 等)本质上也是在构造 EntityResponse<T> 和 EntitiesResponse<T>。如果需要完全控制输出,可以跳过基类方法直接构造。
[HttpGet("GetDetail")]
public ApiResponse GetDetail(long id)
{
var dto = _userService.GetDetail(id);
return new EntityResponse<UserDto>
{
Code = (int)ResponseCode.Tips,
Message = "查询成功,请注意账户状态变更",
MessageType = MessageType.Notice,
Result = dto
};
}
列表场景同理:
[HttpGet("GetList")]
public ApiResponse GetList([FromQuery] PageListRequest request)
{
var (list, total) = _userService.GetList(request);
return new EntitiesResponse<List<UserDto>>
{
Code = 200,
Message = "OK",
Result = new EntitiesResult<List<UserDto>>
{
List = list,
Total = total,
CurrentIndex = request.PageIndex
}
};
}
方式三:封装项目级基类方法
如果项目中有大量重复的自定义响应逻辑,推荐封装一个项目专属的 Controller 基类:
using Aegis.Core.Infrastructure.Controller;
using Aegis.Transfer.Enumerates;
using Aegis.Transfer.Responses;
public class MyAppControllerBase : ApiControllerBase
{
/// <summary>
/// 返回带 TraceId 的成功结果
/// </summary>
protected ApiResponse SuccessWithTrace<T>(T entity)
{
return new EntityResponse<T>
{
Code = 200,
Message = "OK",
Result = entity
};
}
/// <summary>
/// 返回带额外统计信息的列表
/// </summary>
protected ApiResponse SuccessListWithSummary<T>(
T list, long total, string summary) where T : class, IEnumerable, new()
{
return ListResult(ResponseCode.Success, summary, list, total);
}
}
业务 Controller 继承这个基类即可:
[ApiController]
[Route("api/[controller]")]
public class OrderController : MyAppControllerBase
{
[HttpGet("GetOrder")]
public async Task<ApiResponse> GetOrder(long id)
{
var order = await _orderService.GetAsync(id);
return SuccessWithTrace(order);
}
}
什么时候该扩展
| 场景 | 推荐做法 |
|---|---|
| 只需要返回单对象/列表/失败 | 直接用基类方法 |
| 需要在响应中加固定字段(如 TraceId、ServerTime) | 继承 ApiResponse 定义自定义类型 |
| 某些接口需要特殊 Code 或 MessageType | 直接构造 EntityResponse<T> |
| 多个 Controller 都有相同的自定义返回逻辑 | 封装项目级 ControllerBase |
扩展时注意
- 所有响应类型最终都继承
ApiResponse,Controller 方法签名统一返回Task<ApiResponse>即可兼容 EntitiesResult<T>中CurrentIndex默认值为 0,序列化时会被忽略(JsonIgnore(Condition = WhenWritingDefault))- 如果前端依赖固定的 JSON 结构,新增字段不要放在
Result里面改,而是直接在自定义响应类型的同级添加
实际项目里怎么用最省事
在业务项目里,最省事的写法通常是:
- Controller 统一返回
Task<ApiResponse> - 单对象使用
SuccessResult - 列表使用
SuccessListResult - 失败使用
FailedResult - 复杂 Service 分支可考虑
CallResult - 文件导出使用
FileResult
这样前后端都比较好对齐,Swagger 里也更稳定。
这里要特别记一下
当前 Aegis 的基础响应类型是 ApiResponse / EntityResponse<T> / EntitiesResponse<T>。如果你在其他规范材料里看到 ApiResult<T> 之类的写法,请先把它视为参考口径,不要直接覆盖当前代码事实。