• 第三节:框架前期准备篇之利用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 }
    View Code

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

     

    三. 自我改造

       有了前面的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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    c函数调用过程
    查找匹配行及前后5行
    指向类成员函数的指针
    C++箴言:理解typename的两个含义
    不定参数
    定时器
    unix编译
    sed
    大脑皮层的梦工厂
    静态链接库顺序问题
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/9518725.html
Copyright © 2020-2023  润新知