ASP.NET Core 的返回体系分为两层:方法签名类型 (你写在代码里的)和 底层结果类(框架实际执行的)。以下将两者结合,按功能分类提供详细的使用方法和场景示例。
一、 方法签名层面的 4 大返回类型
这是定义 Action 时使用的类型,决定了方法的灵活性和 Swagger 文档生成能力。
| 签名类型 | 说明 | 适用场景 |
|---|---|---|
特定类型 (T) |
直接返回 POCO 或基元类型,框架自动序列化为 JSON (200 OK) | 无异常分支的简单查询、健康检查 |
| IActionResult | 弱类型接口,可返回任意 ActionResult 派生类 | 需返回文件、视图、重定向或多种不同状态码 |
| *ActionResult* | 强类型,兼顾 T 和 IActionResult 的能力 |
现代 Web API 首选,Swagger 自动推断模型 |
| *IAsyncEnumerable* | 异步流式集合返回 | 海量数据列表,避免内存缓冲,支持 Streaming |
二、 底层具体返回结果类(Complete Action Results)
以下是 ControllerBase 中所有便捷方法对应的底层类,按功能分组:
1. 数据与内容类
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| OkObjectResult | Ok(obj) |
200 | return Ok(new { Name = "张三" }); |
请求成功,返回 JSON 数据 |
| OkResult | Ok() |
200 | return Ok(); |
请求成功,无需返回数据 |
| JsonResult | Json(obj) |
200 | return Json(data, new JsonSerializerOptions()); |
强制返回 JSON,忽略内容协商 |
| ContentResult | Content(text, contentType) |
200 | return Content("<xml>...</xml>", "application/xml"); |
返回纯文本、XML、HTML 片段等非 JSON 字符串 |
| ObjectResult | StatusCode(code, obj) |
自定义 | return StatusCode(418, "I'm a teapot"); |
需要自定义状态码并携带响应体 |
2. 资源创建类
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| CreatedResult | Created(uri, obj) |
201 | return Created("/api/products/5", product); |
创建成功,手动指定 Location URL |
| CreatedAtActionResult | CreatedAtAction(action, routeValues, obj) |
201 | return CreatedAtAction(nameof(GetById), new { id = 5 }, product); |
创建成功,根据 Action 名自动生成 Location |
| CreatedAtRouteResult | CreatedAtRoute(routeName, routeValues, obj) |
201 | return CreatedAtRoute("GetProduct", new { id = 5 }, product); |
创建成功,根据路由名称生成 Location |
| AcceptedResult | Accepted() / Accepted(obj) |
202 | return Accepted(new { TaskId = "abc-123" }); |
请求已接受但未处理完(异步任务) |
| AcceptedAtActionResult | AcceptedAtAction(...) |
202 | return AcceptedAtAction(nameof(GetStatus), new { taskId = "abc" }); |
异步任务,附带状态查询地址 |
3. 客户端错误类
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| BadRequestResult | BadRequest() |
400 | return BadRequest(); |
请求参数格式错误 |
| BadRequestObjectResult | BadRequest(obj) |
400 | return BadRequest(new { Error = "Name is required" }); |
请求参数错误,附带错误详情 |
| UnauthorizedResult | Unauthorized() |
401 | return Unauthorized(); |
未认证或凭据无效 |
| ForbidResult | Forbid() |
403 | return Forbid(); |
已认证但无权限 |
| NotFoundResult | NotFound() |
404 | return NotFound(); |
资源不存在 |
| NotFoundObjectResult | NotFound(obj) |
404 | return NotFound(new { Message = "Product not found" }); |
资源不存在,附带提示信息 |
| ConflictResult | Conflict() |
409 | return Conflict(); |
资源冲突(如重复创建) |
| ConflictObjectResult | Conflict(obj) |
409 | return Conflict(new { Message = "Duplicate email" }); |
资源冲突,附带冲突详情 |
| UnprocessableEntityResult | UnprocessableEntity() |
422 | return UnprocessableEntity(); |
格式正确但语义错误 |
| UnprocessableEntityObjectResult | UnprocessableEntity(obj) |
422 | return UnprocessableEntity(ModelState); |
模型验证失败,返回验证错误详情 |
4. 无内容类
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| NoContentResult | NoContent() |
204 | return NoContent(); |
PUT/DELETE 成功,无需返回体 |
5. 文件下载类(FileResult 派生类)
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| FileContentResult | File(byte[], contentType, fileName) |
200 | return File(bytes, "image/png", "logo.png"); |
内存中的小文件下载 |
| FileStreamResult | File(stream, contentType, fileName) |
200 | return File(fileStream, "application/pdf", "report.pdf"); |
大文件流式下载,避免内存溢出 |
| PhysicalFileResult | PhysicalFile(path, contentType, fileName) |
200 | return PhysicalFile("/data/report.pdf", "application/pdf"); |
根据服务器物理路径返回文件 |
| VirtualFileResult | File(virtualPath, contentType) |
200 | return File("~/files/report.pdf", "application/pdf"); |
根据虚拟路径返回文件 |
6. 重定向类
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| RedirectResult | Redirect(url) |
302 | return Redirect("https://example.com"); |
临时重定向到指定 URL |
| RedirectResult | RedirectPermanent(url) |
301 | return RedirectPermanent("/new-page"); |
永久重定向 |
| RedirectToActionResult | RedirectToAction(action, controller, routeValues) |
302 | return RedirectToAction("Index", "Home"); |
重定向到指定 Action |
| RedirectToRouteResult | RedirectToRoute(routeName, routeValues) |
302 | return RedirectToRoute("Default", new { id = 5 }); |
根据路由名称重定向 |
| LocalRedirectResult | LocalRedirect(url) |
302 | return LocalRedirect("/dashboard"); |
仅允许本地重定向,防止开放重定向攻击 |
7. 视图类(传统 MVC)
| 底层类 | 便捷方法 | HTTP 状态码 | 使用方法与示例 | 使用场景 |
|---|---|---|---|---|
| ViewResult | View(viewName, model) |
200 | return View("Detail", product); |
渲染 Razor 页面 |
| PartialViewResult | PartialView(viewName, model) |
200 | return PartialView("_ProductCard", product); |
渲染局部视图片段 |
三、 选择决策树
需要返回什么?
├── 纯 JSON 数据 + 可能有错误状态码 → ActionResult<T> ✅ (首选)
├── 文件下载 / 视图 / 重定向 → IActionResult
├── 纯文本 / XML / HTML 字符串 → ContentResult (Content())
├── 绝对不会出错的简单查询 → 特定类型 T
├── 海量数据列表 → IAsyncEnumerable<T>
└── 自定义状态码 → StatusCode(code, value)
四、 关键注意事项
- Swagger 文档 :使用
IActionResult时,必须用[ProducesResponseType]标注所有可能的返回类型和状态码,否则 Swagger 无法生成正确的文档。ActionResult<T>会自动推断 200 的模型,只需标注非 200 的状态码。 - ContentResult vs JsonResult :
Content()返回的是原始字符串,不会经过 JSON 序列化;Json()会将对象序列化为 JSON。不要混淆。 - 文件下载安全 :永远不要直接将用户输入作为文件路径传给
PhysicalFile(),必须进行路径校验,防止目录遍历攻击。优先使用FileStreamResult配合受控的文件访问逻辑。 - 204 No Content :
NoContent()返回时,响应体必须为空。如果框架检测到有内容,可能会抛出异常或忽略内容。 - 异步流 :使用
IAsyncEnumerable<T>时,确保使用System.Text.Json作为格式化程序(默认即是),Newtonsoft.Json 不支持流式序列化。