ASP.NET MVC小改裝 - 以Json.NET取代JavaScriptSerializer
1 | 35,293 |
在前篇文章中,我們實測驗證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 可以設定,當序列化時可以直接被轉為小寫?