目录
在ASP.NET Core 开发中,Controller 层是处理前端请求的 “中转站”,而 Action 方法的返回值则是 “中转站” 给前端的 “快递包裹”——JsonResult 作为 AJAX 交互的核心返回类型,几乎是前后端分离项目的标配,但新手很容易在使用时踩坑,比如返回格式不对、循环引用报错、数据序列化异常等。今天就带大家彻底搞懂 JsonResult,从代码示例到避坑指南,用最通俗的方式讲明白!

一、先搞懂:JsonResult 到底是什么?(生活类比)
先举个生活例子:你(前端)去奶茶店(Controller)点一杯珍珠奶茶(请求数据),店员(Action 方法)不会直接把奶茶原料给你,而是按标准杯型、甜度、冰度(JSON 格式)打包好递给你 ——JsonResult 就是这个 “标准化打包的奶茶”,它把后端数据按 JSON 格式序列化,返回给前端 AJAX 请求,保证前后端能 “看懂” 彼此的数据。
核心定义: JsonResult 是ASP.NET Core 中专门用于返回 JSON 格式数据的 Action 返回类型,继承自 ActionResult,内置 JSON 序列化逻辑,是 AJAX/axios/fetch 等前端请求的 “最佳搭档”。
二、JsonResult 核心用法(代码示例 + 场景)
2.1 基础用法:返回简单 JSON 数据
场景:前端 AJAX 请求获取用户信息
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace JsonResultDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
// 基础版:返回单个对象
[HttpGet("getuser/{id}")]
public JsonResult GetUser(int id)
{
// 模拟从数据库查询用户数据
var user = new
{
Id = id,
Name = "张三",
Age = 28,
Phone = "13800138000",
IsVip = true
};
// 直接返回JsonResult,框架自动序列化
return new JsonResult(user);
}
// 进阶版:返回集合数据
[HttpGet("getusers")]
public JsonResult GetUsers()
{
var userList = new List<object>
{
new { Id = 1, Name = "张三", Age = 28 },
new { Id = 2, Name = "李四", Age = 30 },
new { Id = 3, Name = "王五", Age = 25 }
};
// 也可以用ControllerBase的Json()快捷方法(推荐)
return Json(new { code = 200, msg = "查询成功", data = userList });
}
}
}
小节: 基础用法核心是 “数据 + 序列化”,Json()快捷方法比直接 new JsonResult 更简洁,且自动继承框架默认的 JSON 配置,新手优先用。
2.2 自定义 JSON 序列化配置
场景:前端需要返回驼峰命名、忽略空值、格式化日期
[HttpGet("getusercustom/{id}")]
public JsonResult GetUserCustom(int id)
{
var user = new
{
UserId = id,
UserName = "张三",
CreateTime = DateTime.Now,
Remark = (string)null // 空值字段
};
// 自定义序列化选项
var jsonOptions = new JsonSerializerOptions
{
// 驼峰命名(前端常用,如userName而非UserName)
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// 忽略空值字段
IgnoreNullValues = true,
// 日期格式化
WriteIndented = true, // 格式化JSON(便于调试)
Converters = { new DateTimeConverter("yyyy-MM-dd HH:mm:ss") }
};
return new JsonResult(user, jsonOptions);
}
// 自定义日期转换器
public class DateTimeConverter : JsonConverter<DateTime>
{
private readonly string _format;
public DateTimeConverter(string format) => _format = format;
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString()!);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}
小节: 自定义配置解决了 “后端大驼峰、前端小驼峰”“空值字段冗余”“日期格式乱码” 等问题,是实际项目中必用的技巧。
2.3 结合状态码返回
场景:请求失败时返回错误码 + 提示信息
[HttpPost("adduser")]
public JsonResult AddUser([FromBody] UserDto userDto)
{
// 模拟参数校验
if (string.IsNullOrEmpty(userDto.Name))
{
// 返回错误状态码+JSON
Response.StatusCode = StatusCodes.Status400BadRequest;
return Json(new { code = 400, msg = "用户名不能为空" });
}
// 模拟添加用户成功
return Json(new { code = 200, msg = "添加成功", data = new { Id = 1001, Name = userDto.Name } });
}
// 定义DTO类
public class UserDto
{
public string Name { get; set; }
public int Age { get; set; }
public string Phone { get; set; }
}
小节: 结合 HTTP 状态码返回,前端能更精准判断请求结果(如 400 参数错误、500 服务器错误),符合 RESTful 规范。
三、JsonResult 使用流程图
四、常踩的坑(附解决方案 + 生活类比)
4.1 坑 1:循环引用导致序列化报错
现象:
返回包含关联对象的数据(如 User 包含 Order,Order 又包含 User),运行时报错:A possible object cycle was detected。
生活类比:
你介绍自己的家庭关系,说 “我是我爸的儿子,我爸是我的爸爸”,无限循环,别人根本听不懂。
解决方案:
// 方案1:忽略循环引用(推荐)
var jsonOptions = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
return new JsonResult(userWithOrder, jsonOptions);
// 方案2:使用DTO隔离关联对象(更规范)
// 定义只包含必要字段的DTO,避免关联对象
public class UserSimpleDto
{
public int Id { get; set; }
public string Name { get; set; }
// 只留需要的字段,不包含Order关联
}
小节: 循环引用是新手最常踩的坑,优先用 DTO 隔离,其次用忽略循环引用配置,避免返回冗余关联数据。
4.2 坑 2:数据类型序列化异常(如 DateTime / 枚举)
现象:
日期返回成2025-11-26T10:00:00(带 T),枚举返回数字而非文字,前端解析混乱。
生活类比:
你给快递员写地址时用 “沪 A12345”,但快递系统只认 “上海市 + 车牌号”,格式不匹配导致快递送错。
解决方案:
// 1. 日期格式化(前面已讲,此处简化)
// 2. 枚举序列化为字符串
var jsonOptions = new JsonSerializerOptions
{
Converters = {
new JsonStringEnumConverter(), // 枚举转字符串
new DateTimeConverter("yyyy-MM-dd HH:mm:ss")
}
};
// 示例:枚举类型
public enum UserType
{
Normal,
Vip,
Admin
}
// Action中返回枚举
[HttpGet("getusertype/{id}")]
public JsonResult GetUserType(int id)
{
var user = new { Id = id, Type = UserType.Vip };
return new JsonResult(user, jsonOptions); // 返回Type:"Vip"而非1
}
小节: 特殊类型(日期、枚举、小数)需自定义序列化规则,保证前后端数据格式一致。
4.3 坑 3:忘记配置 [FromBody] 导致参数接收不到
现象:
前端 POST 传递 JSON 数据,后端 Action 的 DTO 参数全为 null。
生活类比:
你给快递员递包裹,但没填收货地址([FromBody]),快递员不知道该寄去哪,自然拿不到包裹。
解决方案:
// 错误写法:缺少[FromBody]
[HttpPost("adduser")]
public JsonResult AddUser(UserDto userDto) { ... }
// 正确写法:添加[FromBody](POST JSON必须加)
[HttpPost("adduser")]
public JsonResult AddUser([FromBody] UserDto userDto) { ... }
小节: POST 请求接收 JSON 参数时,必须加[FromBody]特性,GET 请求则用[FromQuery]/[FromRoute]。
4.4 坑 4:返回超大 JSON 导致性能问题
现象:
返回上万条数据的 JSON,前端加载慢,后端内存占用高。
生活类比:
你点奶茶时非要店员一次性给你 100 杯,店员忙不过来,你也拿不动,效率极低。
解决方案:
// 方案1:分页返回
[HttpGet("getuserspage")]
public JsonResult GetUsersPage([FromQuery] int page = 1, [FromQuery] int size = 10)
{
// 模拟分页查询
var total = 1000; // 总条数
var userList = new List<object>();
// 分页逻辑:跳过(page-1)*size条,取size条
for (int i = (page-1)*size + 1; i <= page*size; i++)
{
userList.Add(new { Id = i, Name = $"用户{i}", Age = 20 + i%10 });
}
return Json(new {
code = 200,
msg = "查询成功",
data = userList,
total = total, // 总条数
page = page,
size = size
});
}
// 方案2:启用JSON压缩(全局配置)
// Program.cs中添加
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/json" });
});
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
// 启用中间件
app.UseResponseCompression();
小节: 超大 JSON 需分页 + 压缩,既减少前端加载时间,也降低后端服务器压力。
4.5 坑 5:跨域导致前端无法接收 JSON
现象:
前端控制台报错Access to XMLHttpRequest at ‘xxx’ from origin ‘xxx’ has been blocked by CORS policy。
生活类比:
你去另一个小区取快递,但小区保安(浏览器)不让进,因为你不是本小区的(跨域)。
解决方案:
// Program.cs中配置跨域
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin() // 允许所有源(生产环境需指定具体域名)
.AllowAnyMethod() // 允许所有请求方法(GET/POST等)
.AllowAnyHeader(); // 允许所有请求头
});
});
// 启用跨域中间件(必须在UseRouting之后,UseEndpoints之前)
app.UseCors("AllowAll");
小节: 跨域是前端接收 JSON 的 “拦路虎”,开发环境可允许所有源,生产环境需严格指定前端域名,避免安全风险。
五、总结
JsonResult 是ASP.NET Core 前后端 AJAX 交互的核心,掌握它的关键在于:
1.基础用法:用Json()快捷方法返回数据,优先搭配 DTO;
2.自定义配置:解决序列化格式、循环引用等问题;
3.避坑要点:注意 [FromBody]、跨域、分页、特殊类型序列化。
其实 JsonResult 的使用逻辑很简单 —— 就像和前端 “对话”,你得说对方能听懂的话(JSON 格式),别讲废话(冗余数据),别绕圈子(循环引用),别没头没尾(状态码 + 提示)。
六、互动环节
欢迎在评论区分享:
- 你使用 JsonResult 时遇到过哪些奇葩问题?
- 有没有自己的 “避坑小技巧”?
- 想了解哪些更多关于 Action 返回值的内容(如 IActionResult、FileResult 等)?
如果这篇文章对你有帮助,别忘了点赞 + 收藏 + 关注,后续会持续更新ASP.NET Core Controller 层的核心知识点!
1167





