前篇文章中,我們實測驗證ASP.NET MVC 4的JsonResult採用JavaScriptSerializer進行JSON序列化,然而若要100%確認,其實還有更精準的做法--因為ASP.NET MVC 4是一個Open Source專案,Use the source, Luke!

由CodePlex取得ASP.NET MVC的原始程式碼,追蹤Controller.Json()可以查到以下這段,Json()會在內部建立了一個新的JsonResult物件傳回:

        protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
        {
            return new JsonResult
            {
                Data = data,
                ContentType = contentType,
                ContentEncoding = contentEncoding,
                JsonRequestBehavior = behavior
            };
        }

而深入JsonResult類別,可以在ExecuteResult()查出它是使用JavaScriptSerializer無誤!!

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
                String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);
            }
 
            HttpResponseBase response = context.HttpContext.Response;
 
            if (!String.IsNullOrEmpty(ContentType))
            {
                response.ContentType = ContentType;
            }
            else
            {
                response.ContentType = "application/json";
            }
            if (ContentEncoding != null)
            {
                response.ContentEncoding = ContentEncoding;
            }
            if (Data != null)
            {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                if (MaxJsonLength.HasValue)
                {
                    serializer.MaxJsonLength = MaxJsonLength.Value;
                }
                if (RecursionLimit.HasValue)
                {
                    serializer.RecursionLimit = RecursionLimit.Value;
                }
                response.Write(serializer.Serialize(Data));
            }
        }

Json.NET的作者針對ASP.NET MVC的JSON序列化寫過JsonNetResult,原則上在Controller中用它取代JsonResult就可在MVC中改以Json.NET進行序列化,但Controller.Json()預設還是會使用JavaScriptSerializer。在追蹤原始碼的過程,我留意到Controller.Json()被宣告成virtual,換句話說,只要繼承Controller建立客製版Controller類別,覆寫(Override) Json()方法改傳JsonNetResult,就能改變Controller行為,原程式不需修改就能將JavaScriptSerializer抽換成Json.NET!

以下是一個簡單的Controller改裝範例--JsonNetController:
(由於JsonRequestBehavior.DenyGet判斷的部分會用到只有System.Web.Mvc內部才能存取的錯誤息訊文字資源,此處取巧,在發生DenyGet情境時抛回JsonResult觸發原有錯誤)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
 
namespace Newtonsoft.Json
{
    //REF: http://james.newtonking.com/archive/2008/10/16/asp-net-mvc-and-json-net.aspx
    public class JsonNetResult : JsonResult
    {
        public JsonSerializerSettings SerializerSettings { get; set; }
        public Formatting Formatting { get; set; }
        public JsonNetResult() 
        { 
            SerializerSettings = new JsonSerializerSettings(); 
        }
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType = 
                !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
            if (ContentEncoding != null)
                response.ContentEncoding = ContentEncoding;
            if (Data != null)
            {
                JsonTextWriter writer = new JsonTextWriter(response.Output)
                {
                    Formatting = Formatting
                };
                JsonSerializer serializer = JsonSerializer.Create(SerializerSettings);
                serializer.Serialize(writer, Data); writer.Flush();
            }
        }
    }
 
    public class JsonNetController : Controller
    {
        protected override JsonResult Json(object data, string contentType, 
                  Encoding contentEncoding, JsonRequestBehavior behavior)
        {
            if (behavior == JsonRequestBehavior.DenyGet 
                && string.Equals(this.Request.HttpMethod, "GET", 
                                 StringComparison.OrdinalIgnoreCase))
                //Call JsonResult to throw the same exception as JsonResult
                return new JsonResult();
            return new JsonNetResult()
            {
                Data = data,
                ContentType = contentType,
                ContentEncoding = contentEncoding
            };            
        }
    }
}

以前篇文章的BooController為例,我們將繼承來源由Controller改為JsonNetController,原有的Json(DateTime)寫法不需修改,但輸出結果就會改為Json.NET的版本:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;
using Newtonsoft.Json;
 
namespace MyMvc.Controllers
{
    public class BooController : JsonNetController
    {
        public ActionResult TestJson()
        {
            return Json(DateTime.Now, JsonRequestBehavior.AllowGet);
        }
 
        public ActionResult TestJsonNotAllowGet()
        {
            return Json(DateTime.Now);
        }
 
        public ActionResult DateJsonWtf()
        {
            WebClient wc = new WebClient();
            Uri url = Request.Url;
            string s = wc.DownloadString(
                string.Format("http://{0}:{1}/Boo/TestJson", 
                url.Host, url.Port));
            JavaScriptSerializer jss = new JavaScriptSerializer();
            DateTime d = jss.Deserialize<DateTime>(s);
            return Content(string.Format(
                "Now={0:MM-dd HH:mm}, Json={2}, Restored DateTime={1:MM-dd HH:mm}",
                DateTime.Now, d, s));
        }
 
        public ActionResult TestJsonNetResult()
        {
            return new JsonNetResult()
            {
                Data = DateTime.Now
            };
        }
    }
}

測試JsonRequestBehavior.DenyGet的情境,可得到與JsonResult相同的錯誤訊息:

Json(DateTime.Now)傳回結果則已變成Json.NET的ISO 8601格式:

測試在C#端以JavaScriptSerializer還原TestJson的傳回結果,資料正確無時區差異。另外,此一結果也驗證了JavaScriptSerilalizer能正確地將"2012-08-30T05:04:38.1348135+08:00"的ISO 8601格式反序列化回DateTime。

JavaScriptSerializer在先前的效能測試中,慘敗給Json.NET及DataContractJsonSerializer,而其獨有的DateTime編碼格式,在JavaScript等異質Client端需要額外處理,缺少時區資訊衍生的轉換地雷更是惱人,歸納下來,和IE6一樣,己成該從地表消失的禍水~ 對於新建專案,若無相容性的包袱,建議一律改用Json.NET,方為王道。


Comments

# by 小黑

感謝黑哥的分享,另有一個問題想請教;前陣子小弟測試google service api ,同樣的會傳出 json 格式資料,只是它的資料 屬性欄位皆為 英文小寫,想說當我們在設計 Entities 時,是否有方便 attribute 可以設定,當序列化時可以直接被轉為小寫?

Post a comment