【ASP.NET Core】Controller 层 Action 返回值精讲:JsonResult(AJAX 交互核心)

编程达人挑战赛·第4期 10w+人浏览 298人参与

在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 使用流程图

前端AJAX请求
Controller接收请求
Action方法处理业务逻辑
数据是否合法?
设置错误状态码,构造错误JSON
构造正常业务数据
配置JSON序列化选项 可选
返回JsonResult
框架序列化JSON并响应
前端接收并解析JSON

四、常踩的坑(附解决方案 + 生活类比)

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 层的核心知识点!
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值