一. 背景html
這裏簡單的分析一下JsonResult的源碼:前端
①:繼承了ActionResult, 實現了ExecuteResult方法。java
②:解讀源碼可知,JsonResult內部實現原理是調用了JavaScriptSerializer對象中的Serialize方法,將Json對象轉換成了Json字符串,經過:response.Write(javaScriptSerializer.Serialize(this.Data)); 傳遞給前臺。json
③:默認是禁止Get請求訪問的. JsonRequestBehavior.DenyGet。app
④:在MVC的Action中,return Json(),這裏的Json經過源碼可知,即new了一個JsonResult對象而已,而且MVC中封裝了不少重載。框架
本節涉及到的知識點有:ide
1. MVC中的各類Result,可參考:http://www.cnblogs.com/yaopengfei/p/7910767.html函數
2. MVC中的過濾器,可參考:http://www.javashuo.com/article/p-ecarhzao-dn.htmloop
二. 測試JsonResult的弊端post
這裏主要測試一下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()便可。
測試結果:
!