本文首次發(fā)表在博客[C#之XML反序列化的一些坑]
背景
我們?cè)?net平臺(tái)反序列化一份xml文檔的時(shí)候經(jīng)常性的會(huì)遇到一些很奇怪的問(wèn)題,要么在反序列化過(guò)程中拋出各種異常,要么沒(méi)有發(fā)生異常,可結(jié)果卻不是我們想要的。拋異常的情況倒是比較容易解決,根據(jù)異常信息自己找MSDN文檔或者Google搜索,一般都能找到解決辦法。麻煩的是沒(méi)有異常結(jié)果卻不理想的情況,這種情況往往需要一步步調(diào)試跟蹤代碼,但是微軟的底層已經(jīng)封裝,調(diào)試起來(lái)很困難。
下面是我在反序列化的時(shí)候遇到過(guò)的一些坑。先上一段需要反序列化的代碼。
目標(biāo)XML
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<xsd:DestinationListResponse
xmlns:xsd="http://response.example.com/xsd">
<xsd:success>true</xsd:success>
<xsd:country xsd1:id="1933312" xsd1:name="Afghanistan" xsd1:nameEn="Afghanistan" xsd:isoCode="AF"
xmlns:xsd1="http://types.example.com/xsd">
<xsd:city xsd1:id="1934162" xsd1:name="Kabul" xsd1:nameEn="Kabul" xsd:isoCode="KBL" xsd:latitude="34.53333282470703" xsd:longitude="69.16666412353516"/>
</xsd:country>
<xsd:country xsd1:id="1933313" xsd1:name="Albania" xsd1:nameEn="Albania" xsd:isoCode="AL"
xmlns:xsd1="http://types.example.com/xsd">
<xsd:city xsd1:id="31959649" xsd1:name="Durres" xsd1:nameEn="Durres" xsd:isoCode="TIA" xsd:latitude="41.31666564941406" xsd:longitude="19.450000762939453"/>
<xsd:city xsd1:id="50001732" xsd1:name="Pogradec" xsd1:nameEn="Pogradec" xsd:isoCode="" xsd:latitude="40.900001525878906" xsd:longitude="20.649999618530273"/>
</xsd:country>
</xsd:DestinationListResponse>
</soapenv:Body>
</soapenv:Envelope>
這一段XML,相信大家都能看懂。這里涉及到幾個(gè)XML的概念如下:聲明、名稱(chēng)空間、前綴、元素、屬性。對(duì)這幾個(gè)概念不了解的自行Google。了解這幾個(gè)概念之后,構(gòu)建C#實(shí)體類(lèi)就很簡(jiǎn)單了,復(fù)雜元素構(gòu)建成類(lèi),簡(jiǎn)單元素和屬性作為類(lèi)的字段。我創(chuàng)建了對(duì)應(yīng)的實(shí)體類(lèi)之后開(kāi)始反序列化就遇到了一些問(wèn)題。
一、獲取SOAP中Body里的內(nèi)容報(bào)錯(cuò)或者為空
我這里需要獲取DestinationListResponse元素反序列化成我建的對(duì)應(yīng)的C#實(shí)體DestinationListResponse類(lèi)的對(duì)象。代碼如下:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var node = doc.SelectSingleNode("http://Body");
var innerXml = node.InnerXml;
這樣沒(méi)有報(bào)錯(cuò),但是獲取到node的值=null。然后我把var node = doc.SelectSingleNode("http://Body");這句代碼改成var node = doc.SelectSingleNode("http://soapenv:Body");之后再調(diào)試。這句代碼直接就拋出異常了,提示類(lèi)似缺少名稱(chēng)空間之類(lèi)的信息。然后我發(fā)現(xiàn)SelectSingleNode正好有個(gè)參數(shù)類(lèi)型是管理名稱(chēng)空間的,我就在這句代碼之前把所有名稱(chēng)空間全部加上,最后完整的代碼就長(zhǎng)這樣:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("xsd", "http://response.example.com/xsd");
nsmgr.AddNamespace("xsd1", "http://types.example.com/xsd");
var node = doc.SelectSingleNode("http://soapenv:Body");
var innerXml = node.InnerXml;
這下,終于node終于有值了。然后我藏尸著不把名稱(chēng)空間全部加上,少一個(gè)或者兩個(gè),SelectSingleNode方法都會(huì)拋異常。我又嘗試著把這句代碼改成var node = doc.SelectSingleNode("http://Body");,node的值還是null。
以上得出:
- XML有名稱(chēng)空間時(shí)必須把所有名稱(chēng)空間通過(guò)
XmlNamespaceManager管理起來(lái)傳入方法SelectSingleNode中。 - xml節(jié)點(diǎn)有前綴時(shí),獲取節(jié)點(diǎn)必須帶前綴,如
doc.SelectSingleNode("http://soapenv:Body")而不能是doc.SelectSingleNode("http://Body")。
二、反序列化XML之后,集合元素對(duì)應(yīng)的字段的值為空
如目標(biāo)XML中city元素是多個(gè)一組的,我稱(chēng)之為集合元素。對(duì)應(yīng)的實(shí)體類(lèi)型的字段也應(yīng)該是集合類(lèi)型的,我首先想到的就是使用數(shù)組,給字段標(biāo)記XmlArray特性,如下:
[XmlArray]
[XmlArrayItem(ElementName ="city")]
public CityClass[] CityArr { get; set; }
但是,這樣定義的字段在反序列化XML之后CityArr的值是null的,必須改成下面這樣的才能正確的得到city元素的內(nèi)容
[XmlElement(ElementName = "city")]
public CityClass[] CityArr { get; set; }
三、反序列化XML之后,含前綴的元素的屬性對(duì)應(yīng)的字段的值為空
出現(xiàn)這種情況,大概率有2個(gè)原因。
- 實(shí)體類(lèi)沒(méi)有指定名稱(chēng)空間
- 實(shí)體類(lèi)字段沒(méi)有指定強(qiáng)制使用名稱(chēng)空間前綴
如目標(biāo)XML所有元素都有前綴,而且前綴還不一樣,有xsd和xsd1。前綴和名稱(chēng)空間是一一對(duì)應(yīng)的,目標(biāo)XML中xsd前綴對(duì)應(yīng)http://response.example.com/xsd名稱(chēng)空間,xsd1前綴對(duì)應(yīng)http://types.example.com/xsd名稱(chēng)空間。如city元素的前綴是xsd。那么它對(duì)應(yīng)的實(shí)體就要像下面這樣加上名稱(chēng)空間。
[Serializable]
[XmlType(Namespace = "http://response.example.com/xsd")]
public class CityClass
{
[XmlAttribute(Form = XmlSchemaForm.Qualified)]
public string isoCode { get; set; }
......
}
這段代碼中isoCode的XmlAttribute特性加了一句代碼Form = XmlSchemaForm.Qualified 它的作用就是強(qiáng)制使用名稱(chēng)空間前綴的意思。如果xml文檔的元素或者元素屬性有前綴,那么加上這句就可以正確的反序列化了。不過(guò),如果是元素的化,好像不加這句也沒(méi)有什么影響。
浙公網(wǎng)安備 33010602011771號(hào)