第三节:框架前期准备篇之利用Newtonsoft.Json改造MVC默认的JsonResult

一. 背景

  在MVC框架中,我们可能经常会用到 return Json(),而Json方法内部又是一个JsonResult类,那么JsonResult内部又是什么原理呢?在MVC框架中,各种xxxResult便捷了我们的开发,但这些都不是本节的重点,在这里我们只需要知道JsonResult内部的原理即可。

  JsonResult内部原理是基于 JavaScriptSerializer来做的序列化,在使用过程中,有这么几个弊端:

  ①:DateTime类型返回给前端是这个玩意:\/Date(1535009968228)\/ ,相当别扭。(PS:前端有很多办法处理的)

  ②:对于前端而言,对于属性名可能更倾向于小写开头,但在C#中,很多都是大写,但JsonResult将原结果默认返回给前端,前端人员可能会有点小不爽。(PS:这也可以算作是一个习惯问题,没有明确的对错)

  ③:循环引用的问题。

 关于使用Newtonsoft.Json改造MVC默认的JsonResult,有很多种方式,本节仅是整理了一下在我日常开发中的使用方法。(PS:这里的MVC版本为: 5.2.4.0)

  这里简单的分析一下JsonResult的源码:

①:继承了ActionResult, 实现了ExecuteResult方法。

②:解读源码可知,JsonResult内部实现原理是调用了JavaScriptSerializer对象中的Serialize方法,将Json对象转换成了Json字符串,通过:response.Write(javaScriptSerializer.Serialize(this.Data)); 传递给前台。

③:默认是禁止Get请求访问的. JsonRequestBehavior.DenyGet。

④:在MVC的Action中,return Json(),这里的Json通过源码可知,即new了一个JsonResult对象而已,并且MVC中封装了很多重载。

  本节涉及到的知识点有:

    1. MVC中的各种Result,可参考:http://www.cnblogs.com/yaopengfei/p/7910767.html

    2. MVC中的过滤器,可参考:https://www.cnblogs.com/yaopengfei/p/7910763.html

二. 测试JsonResult的弊端

  这里主要测试一下DateTime类型“乱码”问题和默认大小写问题。

后台代码:

 1     public ActionResult Json1()
 2     {
 3        var msg = new
 4        {
 5           ID = 1,
 6           Name = "ypf1",
 7           time = DateTime.Now
 8        };
 9        return Json(msg);
10    }

前台代码:

1   $("#btn1").on("click", function () {
2        $.post("Json1", {}, function (data) {
3              console.log(data);
4        });
5    });

测试结果:

下面提供一种解决时间乱码的问题,使用该js文件,对Date类型进行扩展,代码如下:

 1 /**
 2  * 对Date的扩展,将 Date 转化为指定格式的String
 3  * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 可以用 1-2 个占位符
 4  * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
 5  * eg:
 6  * (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
 7  * (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04
 8  * (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04
 9  * (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04
10  * (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
11
12 使用:(eval(value.replace(/\/Date\((\d+)\)\//gi, "new Date($1)"))).pattern("yyyy-M-d h:m:s.S");
13
14  */
15 Date.prototype.pattern = function (fmt) {
16     var o = {
17         "M+": this.getMonth() + 1, //月份
18         "d+": this.getDate(), //日
19         "h+": this.getHours() % 12 == 0 ? 12 : this.getHours() % 12, //小时
20         "H+": this.getHours(), //小时
21         "m+": this.getMinutes(), //分
22         "s+": this.getSeconds(), //秒
23         "q+": Math.floor((this.getMonth() + 3) / 3), //季度
24         "S": this.getMilliseconds() //毫秒
25     };
26     var week = {
27         "0": "/u65e5",
28         "1": "/u4e00",
29         "2": "/u4e8c",
30         "3": "/u4e09",
31         "4": "/u56db",
32         "5": "/u4e94",
33         "6": "/u516d"
34     };
35     if (/(y+)/.test(fmt)) {
36         fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
37     }
38     if (/(E+)/.test(fmt)) {
39         fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "/u661f/u671f" : "/u5468") : "") + week[this.getDay() + ""]);
40     }
41     for (var k in o) {
42         if (new RegExp("(" + k + ")").test(fmt)) {
43             fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
44         }
45     }
46     return fmt;
47 }

在前端这么使用,就可以将时间转换成正常的显示:(详细的见上面的代码)

三. 自我改造

  有了前面的JsonResult的代码分析,这里先写一种最简单粗暴的改造方式,当然需要实现安装 Newtonsoft.Json程序集。

改造方案一:

  新建YpfSimpleJsonResult类,继承ActionResult类,利用构造函数传递数据,override ExecuteResult方法,在里面利用Newtonsoft进行改写,代码如下:

 1     /// <summary>
 2     /// 简洁版的改写,只是替换了实现方式
 3     /// </summary>
 4     public class YpfSimpleJsonResult : ActionResult
 5     {
 6         private object _Data = null;
 7         public YpfSimpleJsonResult(object data)
 8         {
 9             this._Data = data;
10         }
11         public override void ExecuteResult(ControllerContext context)
12         {
13             context.HttpContext.Response.ContentType = "application/json";
14             context.HttpContext.Response.Write(JsonConvert.SerializeObject(this._Data));
15         }
16     }

测试接口:

 1    public ActionResult Json3()
 2         {
 3             var msg = new
 4             {
 5                 ID = 1,
 6                 Name = "ypf1",
 7                 time = DateTime.Now
 8             };
 9             return new YpfSimpleJsonResult(msg);
10         }

测试结果:

改造方案二:

  有了上面的方案的基础,下面深度改造一下,新建YpfJsonResult类,直接继承高层封装JsonResult类,并配置引用问题、默认小写问题、自定义时间格式,代码如下:

 1  public class YpfJsonResult : JsonResult
 2     {
 3         public YpfJsonResult()
 4         {
 5             Settings = new JsonSerializerSettings
 6             {
 7                 //1. 忽略循环引用问题,建议设置为Error,这样的话遇到循环引用的时候报错
 8                 ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
 9                 //2. 日期格式化,这里可以将Newtonsoft默认的格式进行修改
10                 DateFormatString = "yyyy-MM-dd HH:mm:ss",
11                 //3. 设置属性为开头字母小写的驼峰命名
12                 ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
13             };
14         }
15
16         public JsonSerializerSettings Settings { get; private set; }
17
18         public override void ExecuteResult(ControllerContext context)
19         {
20             if (context == null)
21             {
22                 throw new ArgumentNullException("context");
23             }
24             if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
25             {
26                 throw new InvalidOperationException("GET is not allowed");
27             }
28             HttpResponseBase response = context.HttpContext.Response;
29             response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType;
30             if (this.ContentEncoding != null)
31             {
32                 response.ContentEncoding = this.ContentEncoding;
33             }
34             if (this.Data == null)
35             {
36                 return;
37             }
38             var scriptSerializer = JsonSerializer.Create(this.Settings);
39             scriptSerializer.Serialize(response.Output, this.Data);
40         }
41     }

测试接口:

 1    public ActionResult Json2()
 2    {
 3       var msg = new
 4        {
 5           ID = 1,
 6           Name = "ypf1",
 7           time = DateTime.Now
 8        };
 9       //注意:这里的Data是JsonResult类中的一个获取和设置数据的属性。
10       return new YpfJsonResult() { Data = msg };
11    }

测试结果:

总结:

  虽然我们通过第二套方案已经达到了我们的目的,但它存在一个弊端,就是侵入性太强,每个方法中都要改写,那么有没有一种方式可以全局控制呢?

  显然是有的,可以考虑使用全局过滤器。

四. 全局处理

  这里换一种思路,通过注册一个全局过滤器,对每个Action进行监测,如果使用的是JsonResult,就把JsonResult替换成自己编写的YpfJsonResult,这样的话业务中的调用代码,不需要发生任何变化,仍然可以使用 return Json()方法。

  特别注意:这里的过滤器要使用行为过滤器,并且要在OnActionExecuted方法中进行业务的编写。(这是过滤器执行顺序决定的)

代码分享:

 1   public class YpfJsonFilter: ActionFilterAttribute
 2     {
 3
 4         public override void OnActionExecuted(ActionExecutedContext filterContext)
 5         {
 6             if (filterContext.Result is JsonResult
 7                 && !(filterContext.Result is YpfJsonResult))
 8             {
 9                 JsonResult jsonResult = (JsonResult)filterContext.Result;
10                 YpfJsonResult jsonNetResult = new YpfJsonResult();
11                 jsonNetResult.ContentEncoding = jsonResult.ContentEncoding;
12                 jsonNetResult.ContentType = jsonResult.ContentType;
13                 jsonNetResult.Data = jsonResult.Data;
14                 jsonNetResult.JsonRequestBehavior = jsonResult.JsonRequestBehavior;
15                 jsonNetResult.MaxJsonLength = jsonResult.MaxJsonLength;
16                 jsonNetResult.RecursionLimit = jsonResult.RecursionLimit;
17                 filterContext.Result = jsonNetResult;
18             }
19         }
20
21
22     }

过滤器代码

编写完过滤器后,需要全局注册一下:

  可以在在FilterConfig文件中注册 filters.Add(new YpfJsonFilter());

  或者直接去:Global文件中:GlobalFilters.Filters.Add(new YpfJsonFilter()); 代码来注册,道理都一样

接口代码,不需要做任何改变,继续沿用return Json()即可。

测试结果:

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。

原文地址:https://www.cnblogs.com/yaopengfei/p/9518725.html

时间: 2024-10-02 21:20:32

第三节:框架前期准备篇之利用Newtonsoft.Json改造MVC默认的JsonResult的相关文章

asp.net MVC 框架中控制器里使用Newtonsoft.Json对前端传过来的字符串进行解析

下面我用一个实例来和大家分享一下我的经验,asp.net MVC 框架中控制器里使用Newtonsoft.Json对前端传过来的字符串进行解析. using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Web.Mvc; namespace MyWebApp.Controllers { public class TestController : Controller { public A

C#利用newtonsoft.json读取.so配置文件内容

今天花 了点时间来使用 C#读取json文件 ,文件后缀为 .so文件 ,也是基于文件流的形式 获取 对象 ,然后解析; 之所以尝试 使用 json读取 ,是因为其配置文件的格式 更为友好 和方便,直观 且形象,当然 XML也是很方便的; 主要是多了一种读取 配置文件的方式:特记录下来,方便后续项目实际使用: 格式如图: 需要注意的是这种格式需注意编辑: 当然通过代码初始化和写入的话,会自动生成如上的格式的,本文只完成如何读取配置文件的信息: 引用的程序集如: using System; usi

第一节:框架前期准备篇之Log4Net日志详解

一. Log4Net简介 Log4net是从Java中的Log4j迁移过来的一个.Net版的开源日志框架,它的功能很强大,可以将日志分为不同的等级,以不同的格式输出到不同的存储介质中,比如:数据库.txt文件.内存缓冲区.邮件.控制台.ANSI终端.远程接收端等等,我们这里主要介绍最常用的两种:txt文件和数据库. (PS:其它的存储介质详见 http://logging.apache.org/log4net/release/config-examples.html) Log4net将日志分为五

第四节:框架前期准备篇之进程外Session的两种配置方式

一. 基本介绍 1. 背景:Asp.Net默认的Session机制是进程内,存储在服务器端内存中,有这么几个缺点: ①:既然存在内存中,空间有限,不能存储大数据量信息,数据量多的话Session会被挤爆. ②:IIS只要一重启,Session就会丢失,哪怕就是改一下配置文件,IIS也会重启,此时如果客户端有用户通过浏览器正在访问该网站,如果用到Session,原Session是丢失的了,就会报“未将对象引用设置到对象的实例”类似的错误. ③:Session是依赖Cookie来保存SessionI

Farseer.net轻量级开源框架 中级篇:DbFactory数据工厂

导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: 执行SQL语句 下一篇:Farseer.net轻量级开源框架 中级篇: 数据绑定 越讲到后面,我们离基础的语法越远了.看到这篇文章,先恭喜下,说明大家已经能用该框架做日常的开发了. 当然还有一些绑定技巧,比如把枚举.List<Users>绑定到DorpDownList.CheckBoxList.RadioButtonList 并显示中文 在下一篇中再解释. 这一篇中,我们主要讲

Farseer.net轻量级开源框架 中级篇:UrlRewriter 地址重写

导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: Cookies.Session.Request 下一篇:Farseer.net轻量级开源框架 中级篇: BasePage.BaseController.BaseHandler.BaseMasterPage基类说明 MVC有路由在手,对于重写URL那是小菜一碟,更或者说是与身就有的“技能”,而面对.Net的WebForm就比较尴尬了.对UrlRewriter处在相对比较薄弱的环节.

Farseer.net轻量级开源框架 中级篇:探究ORM(Mapping)

导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: SQL执行报告 下一篇:Farseer.net轻量级开源框架 中级篇: Cookies.Session.Request 在Farseer.Net 中 ORM的核心在命名空间:FS.ORM . 目前只有4个类文件.其实这4个文件只要是用来做数据库与实体模型之间的映射关系及缓存.至于SQL生成.不同数据库的驱动支持等 在另外的命名空间:FS.Core 中. 对于ORM来说,我们在转换

Farseer.net轻量级开源框架 中级篇:执行SQL语句

导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: 事务的使用 下一篇:Farseer.net轻量级开源框架 中级篇: DbFactory数据工厂 使用自定义SQL,或者存储过程.仍然使用:DbExecutor ,没错,其实框架最终执行,都是到这个类里把生成的SQL传进来的. 因此你也可以使用自己传进来的SQL进行执行你想要的结果. 我们先看下这里面有哪些方法吧.(其实很像你们以前接触过的:DbHelper....) 1 ///

Farseer.net轻量级开源框架 入门篇:事务的使用中级篇

导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 入门篇: 查询数据详解 下一篇:Farseer.net轻量级开源框架 中级篇: 执行SQL语句 DbExecutor 的使用 使用事务,我们需要用到一个类:DbExecutor 它的命名空间在:FS.Core.Data 中,我们先看下它的参数: 1 /// <summary> 2 /// 构造函数 3 /// </summary> 4 /// <param name=&