Server Side JSON/XML的相關寫法

這幾天碰到一個需要從Server端去發出HTTP拿JSON的需求,與之前全Client端的經驗不一致,開發的過程倒也是零障礙啦,但是為了加速日後開發(免找資料)的速度,還是記錄在這邊會比較妥當。

一開始的問題一定是Server端的HTTP Request物件,承襲.NET 1.1的寫法,在這邊還是習慣使用HttpWebRequest,但經過研究後發現.NET提供了三種物件,這些物件的結構經過研究後如下所示。

System.Net.WebRequest(abstract)
System.Net.HttpWebRequest(implement)
System.Net.WebClient(wrapper)

在考量效能至上的情況下,當然是只能選擇把程式碼實作量最少的WebClient丟掉啦!且拋棄WebClient類別還有一個很重要的點,就是他並沒有把Timeout屬性包進去,當然啦,我們可以實作一個Extension來再包皮,但是這樣作原本的程式碼量少優勢就不見了,這樣一來不還是選擇HttpWebRequest。程式碼的概念如下,如果Timeout的話,會跳到catch裡。

System.Net.HttpWebRequest oWRq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create("JsonURL");
oWRq.Timeout = 1000;	//設定網路對接逾時秒數為一秒
try
{
	System.Net.HttpWebResponse oWRp = (System.Net.HttpWebResponse)oWRq.GetResponse();
	using (System.IO.StreamReader oSR = new System.IO.StreamReader(oWRp.GetResponseStream(), System.Text.Encoding.UTF8))
	{
		string cJSON = oSR.ReadToEnd();
		var oTemp = Newtonsoft.Json.JsonConvert.DeserializeObject<ORM_Class>(cJSON);
		//wrote something...
	}
}
catch
{
	return false;	//如果是逾時或其它原因,一律傳回false
}

JSON的格式種類:a

基本上有些情況你只能毫無怨言的收下對方餵給你的JSON,如果他是一個懂狀況的程式設計師,他可能會餵給你這樣的結構。

{
	{
		"productID":	1,
		"name":	"ABC"
	},
	{
		"productID":	2,
		"name":	"DEF"
	}
}

這樣的結構很好解,基本上你只要準備一套ORM Class跟善用List<T>就可以解決,像是這樣...

//ORM
public class Prodcut
{
	public int productID { get; set; }
	public string name { get; set; }
}
//然後
List<Prodcut> oProdcuts = (List<Prodcut>)JsonConvert.DeserializeObject(cJSON);
//接下來你就可以去count它或操作他了
oProdcuts.Count;
oProdcuts[0];
foreach(var oItem in oProdcuts) { oItem.productID; ... }

JSON的格式種類:b

有時也會有一些毫無sense的人直接把XML轉JSON丟給你了事(留下一個大腸頭、闌尾),但是你又沒有要求的權利,在這裡不考慮多筆的狀況,JSON格式我改成如下:

{
	"product":
	{
		"productID":	1,
		"name":	"ABC"
	}
}

就算是迫於無奈也得接受,這就是程式設計師的宿命,因此你為了這種骯髒的JSON還要再寫一層Wrapper ORM。程式碼如下:

//ORM
public class ProdcutRoot
{
	public Prodcut product { get; set; }
}
public class Prodcut
{
	public int productID { get; set; }
	public string name { get; set; }
}
//然後
var oProdcuts = JsonConvert.DeserializeObject<ProdcutRoot>(cJSON);
//接下來操作是
oProdcuts.products.productID;
oProdcuts.products.name;

※ Update 20150708:

C# 4.0以後有支援dynamic宣告,也就是說,你可以搭配Newtonsoft.Json.JsonConvert.DeserializeObject來進行操作,因此可以省略掉Wrapper ORM的撰寫,以上面為例,可以改寫成下列的程式碼,更顯得超級清爽啊!

//ORM
public class Prodcut
{
	public int productID { get; set; }
	public string name { get; set; }
}
//然後
dynamic oJsonTemp = Newtonsoft.Json.JsonConvert.DeserializeObject(cJSON);
//oJsonTemp.product即為動態運算式,此作業將於執行階段解決
var oProdcut = Newtonsoft.Json.JsonConvert.DeserializeObject<Prodcut>(oJsonTemp.product.ToString());
//接下來操作是
oProdcut.productID;
oProdcut.name;

同場加映:如何避免輸出無謂的XML Root Node給JSON

以上述的那個毫無sense的JSON,直翻回XML應同等如下(型別我就省略了,那不是討論的重點):

<product>
	<productID>1</productID>
	<name>ABC</name>
</product>

翻一下JSON.NET應該可以找到「JsonConvert.SerializeXmlNode Method (XmlNode, Formatting, Boolean)」這個建構子,第三個引數Boolean的宣告如下:omitRootObject/Type: System.Boolean/Omits writing the root object。我想答案已經很明顯了,以下示範如何把這個XML灌入JSON.NET去Parse。

XmlDocument oXML = new XmlDocument();
oXML.LoadXml(@"XML字串;不再綴述");
string cJson = JsonConvert.SerializeXmlNode(oXML, Formatting.None, true);

※ 相關連結

利用匿名型別來化簡JSON ORM讀取時之冗長程式碼

延伸參考:YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE(WebClient因實作IDisposable反而引發連線資源耗盡(TIME_WAIT)問題

ServerSide WebRequest HttpWebRequest WebClient JSON XML RootElement RootNode