ASP.NET Web 方法中的 XmlElement 參數的功能

 

ASP.NET Web 方法中的 XmlElement 參數的功能

發佈日期: 6/17/2004 | 更新日期: 6/17/2004

Matt Powell

Microsoft Corporation

摘要:Matt Powell 探討了多種不同的方式,以便在 Web 服務中使用 XmlElement 參數來訪問原始 XML 數據並獲得高級控制。

*
本頁內容

Web 方法序列化 Web 方法序列化
從 Web 方法中提取 XmlSerializer 從 Web 方法中提取 XmlSerializer
XmlElement 和消息驗證 XmlElement 和消息驗證
更多控制...更困難的代碼 更多控制...更困難的代碼

在 Tim Ewald 的 House of Web Services 專欄文章 Accessing Raw SOAP Messages in ASP.NET Web Services(發表於 MSDN Magazine 第三期)中,Tim 介紹了一種直接使用 SOAP Extension 來操作 SOAP 消息流的有趣方法。這可能是以基於流的方式訪問原始 SOAP 消息的有用方法,並使您能夠完全控制消息分析過程。但是,Tim 還提到了一種更方便的方法,使用該方法可獲得一些相同的好處,但他同時警告,您將訪問的數據已經由內部 ASMX 處理程序分析過一次。這種更方便的方法是:在 Web 方法中使用 XmlElement 參數。在本期的“爲您服務”專欄中,我探討了一些不同的方法,在這些方法中,對 Web 服務使用 XmlElement 參數可能是一種有用的訪問原始 XML 數據的機制,同時,我還探討了如何通過此機制獲得對 Web 服務的高級控制。

Web 方法序列化

在我們詳細討論如何使用 XmlElement 參數操作 Web 方法之前,讓我們首先看一下 Web 方法如何使用 .NET 框架中的 XmlSerializer 來調用方法和生成響應。圖 1 象徵性地說明了在收到 SOAP 消息的 XML 正文時所發生的事情。


1. XmlSerializer 在標準 ASP.NET Web 方法調用中的作用

XML 被傳遞給 XmlSerializer,以便將其反序列化爲託管代碼中的類的實例,而這些實例將映射到 Web 方法的參數。同樣,Web 方法的輸出參數和返回值被序列化爲 XML,以便創建 SOAP 響應消息體。

如果我們創建一個將兩個整數加起來並返回結果的 Web 方法,則託管代碼可能如下所示:

[WebMethod]
public int Add (int x, int y)
{
    return x + y;
}

發送給該 Web 方法的 SOAP 消息將如下所示:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Add 
        xmlns="http://msdn.microsoft.com/AYS/XmlElementService">
      <x>3</x> 
      <y>2</y> 
    </Add>
  </soap:Body>
</soap:Envelope>

因此,XmlSerializer 用於將紅色的 XML 轉換爲上述 Add 方法的參數。因爲 integer 是類型,這一點是很明顯的,但現在我們將看到一個複雜一些的示例。

假設我們有一個 Web 方法,它採用除數據類型以外的其他內容作爲參數。請考慮以下 C# 代碼:

public class MyClass
{
    public string child1;
    public string child2;
}

[WebMethod]
public void SubmitClass(MyClass input)
{
    // Do something with complex input parameters
    return;
}

該方法只採用一個參數,但該參數並不映射到簡單的 XML 類型。實際上,它映射到一個複雜類型,該類型由以下 SOAP 信封中的輸入元素表示:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <SubmitClass
      xmlns="http://msdn.microsoft.com/AYS/XEService">
      <input> 
        <child1>foo</child1> 
        <child2>bar</child2> 
      </input> 
    </SubmitClass>
  </soap:Body>
</soap:Envelope>

XmlSerializer 採用 input 元素作爲參數,並將其反序列化爲 MyClass 類型(該類型在調用 Web 方法之前,在該方法的代碼中定義)的實例。

您應該記住的 .NET 框架中另外一個 Web 服務支持的要點是,開發 Web 服務使用者代碼很容易。這一點能夠成立的原因是:將會自動爲您的 Web 方法創建 WSDL,以便詳細描述 Web 服務接口的細節。如果您查看 ASP.NET 爲該方法生成的 WSDL,您將注意到 input 元素已在 types 節中按如下方式定義:

<s:complexType name="MyClass">
  <s:sequence>
    <s:element minOccurs="0" maxOccurs="1" 
               name="child1" type="s:string" />
    <s:element minOccurs="0" maxOccurs="1" 
               name="child2" type="s:string" />
  </s:sequence>
</s:complexType>

Visual Studio?.NET 中的 Add Web Reference 支持將在客戶端上定義一個與前面的 MyClass 類定義相符合的類,以使您的調用代碼很像是在調用 Web 方法,就如同它是函數調用一樣。下面列出了該方法調用。

localhost.XEService proxy = new localhost.XEService();
localhost.MyClass myInstance = new localhost.MyClass();
myInstance.child1 = "foo";
myInstance.child2 = "bar";
proxy.SubmitClass(myInstance);

XmlSerializer 還用在客戶端上,以便將類實例序列化爲與 WSDL 中定義的架構相符合的 XML。

從 Web 方法中提取 XmlSerializer

在編寫和使用 Web 服務時,XmlSerializer 的功能非常有用並且很強大。它使網絡上的 XML 與託管代碼中的類之間的關係變得透明。這一點通常會很有用,同時這也是 ASP.NET Web 方法成爲編寫 Web 服務最有效方法之一的一個重要原因。

但如果您不喜歡 XmlSerializer 的工作方式該怎麼辦?如果您希望對序列化和反序列化過程進行更多的控制該怎麼辦?或許您不喜歡這樣一個事實:XmlSerializer 不會針對消息的架構對收到的 XML 進行驗證。或許您希望能夠選擇對消息的哪些部分進行反序列化。或許您甚至根本不瞭解傳入消息的架構是什麼樣子。是否有一種簡便的方法能夠避免在默認情況下使用 XmlSerializer,並且自行控制消息反序列化過程?存在幾種解決該問題的方法:我們將集中討論 XmlElement 參數的使用。

XmlElement 的功能

在前面的兩個示例中,我們看到了參數和複雜類如何序列化爲 Web 方法的參數。但如果參數本身就是 XML,會發生什麼事情?

請考慮以下 Web 方法:

[WebMethod]
public void SubmitXmlElement(XmlElement input)
{
    // Do something with the XML input
    return;
}

在該示例中,Web 方法採用 System.Xml.XmlElement 對象作爲輸入。下面的 SOAP 信封可用於向該 Web 服務發送消息。

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <SubmitXmlElement
        xmlns="http://msdn.microsoft.com/AYS/XEService">
      <input>
        <MyClass xmlns="">
          <child1>foo</child1>
          <child2>bar</child2>
        </MyClass>
      </input>
    </SubmitXmlElement>
  </soap:Body>
</soap:Envelope>

注意,我所發送的內容非常類似於我們發送到前面的 Web 方法的內容。然而,主要區別在於:由於 Web 方法的定義,XmlSerializer 將把參數反序列化爲 XmlElement 對象而不是 MyClass 對象。事實是 XmlSerializer 將整個 SOAP 信封反序列化。當它確定 Web 方法的參數的類型是 XmlElement 時,反序列化過程將變得不再重要,而信息集的相應部分將以 XmlElement 的形式提供。

這種方法的不利之處在於:爲該方法定義的自動 WSDL 現在將不會包含有關 input 參數結構的信息。畢竟,Web 方法並不定義 input 參數的結構。這既有好的一面,也有壞的一面。

當您在 Visual Studio .NET 中對該類型的方法使用 Add Web Reference 時,代理類上的 input 參數將被定義爲 XmlNode(它是 XmlElement 的基類)。因此,我們可以向該方法發送我們喜歡的任何 XML。這一點很有意義,因爲如果您查看 WSDL,就會看到 input 元素被定義爲一個複雜類型,該類型的唯一元素的類型爲 xsd:any。肯定在一些業務場合中,將原始的、沒有架構的 XML 發送給 Web 服務確實是一件好事情,但在許多場合下這樣做並不恰當。許多時候,我們的 Web 服務希望數據遵循一種或多種已知的格式。

因此,如果您瞭解參數的格式,爲什麼還要使用 XmlElement 呢?因爲您現在可以自行控制反序列化過程,甚至可以不執行反序列化。這有可能改善性能,並且將有機會進行以下操作:執行 XML 變換、驗證 XML 或執行基於內容的反序列化。

該問題的另外一個方面是如何定義自己的 WSDL,以便其公佈它可以處理多個已知的參數類型。在此我們將不詳細討論該問題,但請參閱 Versioning Options,以獲取有關以下內容的提示:編寫能夠公開多個參數類型的 Web 方法,及其對生成的 WSDL 產生何種影響。

XmlElement 和消息驗證

XmlSerializer 不會驗證它所反序列化的 XML。事實表明,這通常不會是一個大問題。例如,如果某人發送了以下 XML:

<MyClass>
  <param2>foo</param2>
  <param1>bar</param1>
</MyClass>

根據前面爲類定義的架構,它將不會通過驗證。這裏的問題在於,類型被定義爲元素序列,這意味着順序是重要的。當然,我們沒有在代碼中編寫任何表明我們確實關心參數順序的內容,因此如果發送給我們的 XML 沒有經過嚴密的有效性檢查,代碼仍然可能沒有問題。XML 驗證過程的開銷可能比較大,這很可能是默認情況下不對傳入消息執行驗證的原因。

然而,在某些場合下,對消息進行嚴格驗證可能非常重要。例如,如果要對傳入的 XML 執行 XPath 查詢,對 XML 結構進行假設可能導致非常意外的結果。

因此,在將傳入的 XML 反序列化爲類實例之前,可以使用 XmlElement 對其進行驗證。請考慮下面的 Web 方法:

[WebMethod]
public void SubmitValidatedTypedXml([XmlAnyElement]XmlElement input)
{
    XmlSchemaCollection schemaCollection = new XmlSchemaCollection();
    schemaCollection.Add("http://localhost/XEService/MyNewClass.xsd", 
        "http://localhost/XEService/MyNewClass.xsd");
    // XmlValidatingReader only accepts XmlTextReader as input
    // which is why we pass in input.OuterXml instead of an instance
    // of XmlNodeReader.
    XmlValidatingReader validator
        = new XmlValidatingReader(input.OuterXml, 
                                  XmlNodeType.Element, 
                                  null);
    validator.Schemas.Add(schemaCollection);
    XmlSerializer serializer 
        = new XmlSerializer(typeof(MyNewClassType));
    MyNewClassType newInstance 
        = (MyNewClassType)serializer.Deserialize(validator);
    // Do something with MyNewClassType object
}

該 Web 方法做了幾件事情。其目標是獲取傳遞給 Web 方法的 XmlElement,並手動將其作爲名爲 MyNewClassType 的類的實例進行反序列化。要完成該過程的這一部分,請創建 XmlSerializer 類的實例,指示作爲構造函數的參數所涉及的類型,然後調用 Deserialize 方法。

但在這種情況下,我們還做了另外一件事情。Deserialize 方法將 XmlReader 對象作爲輸入。我們可能已經創建了 XmlNodeReader 類(該類繼承 XmlReader)的實例,並將其傳遞給 Deserialize 方法。然而,在此情況下,我們將 XmlValidatingReader 類的實例傳遞給了 Deserialize 方法。您通過 .NET 框架驗證 XML 的方法是使用 XmlValidatingReader。我們通過向 XmlValidatingReader 傳遞 input 參數的 XML 片段,創建了它的實例。爲了針對架構來驗證 XML,驗證程序需要加載該架構,以便了解針對什麼來進行驗證。通過向架構集合中添加已知的架構,可以實現這一目標。

XML 的最終驗證發生於對 XmlSerializer 對象調用 Deserialize 方法時。這是因爲 XmlValidatingReader 也派生自 XmlReader,對所包含的 XML 的驗證是隨着通過 XmlReader 接口讀取各種節點而發生的。在 Deserialize 方法遍歷所有節點以創建 MyNewClassType 類的實例時,將讀取這些節點。

MyNewClassType 類與前面創建的 MyClass 類非常類似,不同之處在於:我使用 Visual Studio 對創建 XSD 文件的支持來定義 XML 架構,從而創建了該類,然後使用 XSD.exe 實用工具並通過以下命令行創建了一個託管類:

Xsd /c MyNewClass.xsd

通過這種方式,我將 XSD 架構傳遞給 XmlValidatingReader,同時傳遞的還有將 XML 反序列化所得到的類代碼。

在該 Web 方法中,還有另外一個在上一版本中未曾包括的重要之處。在該方法中,我們用 XmlAnyElement 參數修飾了 input 參數。這表明該參數將從 xsd:any 元素進行反序列化得到。因爲未向該屬性傳遞任何參數,這意味着 SOAP 消息中該參數的整個 XML 元素將位於由該 XmlElement 參數表示的信息集中。這與前面的 Web 方法稍有不同。讓我們看一下區別在哪裏。

XmlAnyElement 屬性

要了解 XmlAnyElement 屬性的工作方式,讓我們看一下以下兩個 Web 方法:

// Simple Web method using XmlElement parameter
[WebMethod]
public void SubmitXml(XmlElement input)
{return;}

// Simple Web method...using the XmlAnyElement attribute
[WebMethod]
public void SubmitXmlAny([XmlAnyElement] XmlElement input)
{return;}

這兩個方法之間的區別在於:對於第一個方法 SubmitXml,XmlSerializer 期望名爲 input 的元素是 SOAP 體中 SubmitXml 元素的直接子元素。而第二個方法 SubmitXmlAny 則不關心 SubmitXmlAny 元素的子元素的名稱是什麼。它將所包含的任何 XML 插入到 input 參數中。ASP.NET 幫助中有關這兩種方法的消息樣式如下所示。首先,我們看一下不帶 XmlAnyElement 屬性的方法的消息。

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <SubmitXml xmlns="http://msdn.microsoft.com/AYS/XEService">
      <input>xml</input>
    </SubmitXml>
  </soap:Body>
</soap:Envelope>

現在,我們看一下使用 XmlAnyElement 屬性的方法的消息。

<!-- SOAP message for method using XmlAnyElement -- >
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <SubmitXmlAny xmlns="http://msdn.microsoft.com/AYS/XEService">
      Xml 
    </SubmitXmlAny>
  </soap:Body>
</soap:Envelope>

XmlAnyElement 屬性修飾的方法少一個包裝元素。只有與該方法同名的元素包裝了傳遞給 input 參數的內容。

如果您希望爲您的 Web 方法控制 XmlSerializer 的反序列化過程,使用 XmlAnyElement 是一個不錯的竅門,藉此可通過您的自定義邏輯處理消息體的更多內容。但是,我們能否獲取傳遞給 XmlElement 實例的消息體的更多內容?事實上我們能夠做到這一點,方法就是使用 SoapDocumentMethod 屬性的 ParameterStyle 屬性。

[WebMethod]
[SoapDocumentMethod(ParameterStyle = SoapParameterStyle.Bare)]
public void SubmitBody([XmlAnyElement]XmlElement input)
{
    return;
}

通過將 ParameterStyle 屬性設置爲 SoapParameterStyle.Bare 並使用 XmlAnyElement,現在可將消息樣式更改爲以下形式:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>xml</soap:Body>
</soap:Envelope>

現在,SOAP 體的全部內容都將被傳遞給我們的輸入參數。

上述機制在某些方面可能非常有用,但我們應該記住 SOAP 體本身並不是 XML 文檔。特別是,它並非必須具有單個根元素。實際上,WS-I 基本配置文件的確指明 Body 應該具有單個子元素;但是,這一點並不能得到保證。如果我們確實要訪問傳遞給 Web 方法的原始 XML,我們需要考慮消息體可能具有多個子元素的情形,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <name>Fred</name>
    <name>Wilma</name>
    <name>Betty</name>
    <name>Barney</name>
  </soap:Body>
</soap:Envelope>

結果表明,SubmitBody 方法可以接受這種消息,只是沒有辦法訪問所有數據。當消息被反序列化後,您只能通過 input 參數訪問列表中的最後一個元素。

我們可以通過將單個 XmlElement 輸入更改爲 XmlElements 數組來處理這種情形。以下方法將能夠讀取發送給它的任何 SOAP 消息的整個消息體。

[WebMethod]
[SoapDocumentMethodAttribute(ParameterStyle = SoapParameterStyle.Bare)]
public void SubmitAnything([XmlAnyElement]XmlElement [] inputs)
{
    return;
}

更多控制...更困難的代碼

我們已經解決了如何從 ASP.NET Web 方法直接訪問 XML 中的 SOAP 體的全部內容這一問題。這將使您有機會做很多事情,例如,在反序列化 XML 之前針對架構對其進行驗證,避免首先執行反序列化,分析 XML 以確定希望如何對其進行反序列化,以及使用許多功能強大的 XML API 直接處理 XML。這還使您能夠以自己的方式處理錯誤,而不是使用 XmlSerializer 可能在內部產生的錯誤。

對於那些希望對發送給 Web 方法的 XML 進行低級訪問的讀者來說,通過將 XmlElement 屬性與本文介紹的一些 Web 方法屬性竅門結合使用,可以在 Web 服務中獲得更多控制,但這的確需要編寫更多的代碼,並且需要熟悉 .NET 框架中提供的一些基本 XML 技術。不過,.NET 框架的絕妙之處就在於它使開發人員能夠靈活地進行許多低級控制,同時又爲創建和使用 Web 服務提供了簡單而有效的開發環境。

爲您服務

Matt PowellMSDN XML Web Services Developer Center 的首席內容策劃師。他爲 MSDN 和各種雜誌撰寫了大量文章,還與人合著了由 Microsoft Press 出版的 Running Microsoft Internet Information Server 一書。此外,Matt 還協助開發了具有開拓意義的 SOAP Toolkit 1.0。Matt在深入研究 Web 服務之餘,與他的妻子在大西雅圖地區盡享天倫之樂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章