這兩天在工作上遇到一個需求,要將原來A網站上用Ajax-Enabled WCF做出來的Ajax查詢ID的功能提供給Server上的B網站使用。本來這樣的需求可以透過直接在B網站上呼叫A網站的WCF服務來達成,但是考慮一旦這樣做下來,後續2個網站將必需放在同一台Server上,且在開發時期得另外架一個網站提供這樣的服務,並不便宜開發及除錯。於是改為在B網站上,直接透過在B網站的查詢字串在Server端以Post方式直接連到A網站的WCF服務來取得資料,而不在Client端完成(不過這樣相對的也有它的成本需要考量,必需自行取捨)。
這樣的方式之前在其他Case也有做過,不過當時A網站並不是直接透過WCF的做法來提供Ajax的查詢,而是自行寫一隻.AXD(Http Handler)的方式來完成。本以為類似的模式只要依樣畫葫蘆就可以完成,但卻在將WCF轉出的JSON Data再轉回B網站程式中的物件時出現了錯誤『System.ArgumentNullException: 值不能為 null。參數名稱: type』,遇到這樣的問題,先查了一下A網站出來的JSON,格式如下:『 {"__type":"SimpleType:#TestTypeResolver", "ID":"00001","Name":"Jacky"}"』,其中SimpleType 為A網站Server轉出來的物件type; 看到這裡,直覺認為是『__type』這個欄位在作崇,於是先從程式Replace掉再試一次看看...!!果不其然,只要拿掉了__type 欄位,錯誤訊息則不再出現。
得到了這樣的線索之後,問題就變得明確多了;拜了一下G大神之後,雖然沒有得到直接的解決方式,但卻找到一個解決問題的關鍵訊息...『JavaScriptTypeResolver 物件 』! 這個物件依名稱大概可以猜出七八成它的意義,主要是 JavaScriptSerializer 在遇到__type 這樣的meta資訊時,如何將物件Type轉成string 存到__type中,以及反解時將__type中的字串轉回type的功能。 有了這樣的訊息以後問題就容易解了,只要在建立 JavaScriptSerializer ,傳入自行實作的 JavaScriptTypeResolver ,就可以避開這樣的問題。
//訂為private,以免外部程式誤用
private class SimpleTypeResolver : JavaScriptTypeResolver
{
public override Type ResolveType(string id)
{
return typeof(SimpleType); //在這個例子中只做SimpleType的轉換,暫不考慮其他物
}
public override string ResolveTypeId(Type type)
{
throw new NotImplementedException(); //在這個例子中不做Serialize,所以不會被呼叫
}
}
public SimpleType Deserialize(string json){
JavaScriptSerializer serial = new JavaScriptSerializer(new SimpleTypeResolver());
var myType = serial.DeserializeObject(@"{""__type"":""SimpleType:#TestTypeResolver"",""ID"":""00001"",""Name"":""Jacky""}");
}
結論:
原本在使用 JavaScriptSerializer 並沒有留意到內部是透過 JavaScriptTypeResolver 來做 Reflection 的工作,所以遇到這樣情況時,可能一時無法理解原因為何,只能多花時間再去追可能造成問題的原因,若是在開發上能針對使用的物件多花一點心思,不只懂如何使用它,還要瞭解背後的原理或細節,這樣對於各種問題的狀況應該能夠更快的掌握才是!