共用方式為


使用 XmlSerializer 類別

Windows Communication Foundation (WCF) 可以使用兩種不同的串行化技術,將應用程式中的數據轉換成用戶端和服務之間傳輸的 XML: DataContractSerializerXmlSerializer

DataContractSerializer(資料契約序列化器)

根據預設,WCF 使用 DataContractSerializer 類別來序列化數據類型。 此串行化程式支援下列型態:

許多 .NET Framework 類型都屬於後兩個類別,因此可串行化。 可序列化類型的陣列也可以序列化。 如需完整清單,請參閱 在服務合約中指定數據傳輸

DataContractSerializer與數據合約類型一起使用的 ,是撰寫新 WCF 服務的建議方式。 如需詳細資訊,請參閱 使用數據合約

XML序列化器

WCF 也支援 類別 XmlSerializer 。 類別 XmlSerializer 並不特定於 WCF。 這是 ASP.NET Web 服務所使用的相同串行化引擎。 類別 XmlSerializer 支援比 DataContractSerializer 類別更窄的類型集合,但允許對產生的 XML 有更多的控制,並支援更多的 XML 架構定義語言 (XSD) 標準。 它也不需要可串行化型別上的任何宣告式屬性。 如需詳細資訊,請參閱 .NET Framework 檔中的 XML 串行化主題。 類別 XmlSerializer 不支持數據合約類型。

使用 Visual Studio 中的 Svcutil.exe 或 新增服務參考 功能來產生第三方服務的用戶端程式代碼,或存取第三方架構時,系統會自動為您選取適當的串行化程式。 如果架構與 DataContractSerializer 不相容,則會選擇 XmlSerializer

切換至 XmlSerializer

有時,您可能必須手動切換至 XmlSerializer。 例如,在下列情況下,會發生此情況:

  • 將應用程式從 ASP.NET Web 服務移轉至 WCF 時,您可能會想要重複使用現有的相容類型, XmlSerializer而不是建立新的資料合約類型。

  • 當精確控制出現在訊息中的 XML 很重要時,但 Web 服務描述語言 (WSDL) 檔無法使用,例如,建立必須符合特定標準化、已發佈架構且與 DataContractSerializer 不相容之類型的服務時。

  • 建立遵循舊版 SOAP 編碼標準的服務時。

在這些和其他情況下,您可以透過將 XmlSerializer 屬性套用至您的服務,手動切換至 XmlSerializerFormatAttribute 類別,如下列程式碼所示。

[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
[OperationContract]
    public void ProcessTransaction(BankingTransaction bt)
    {
        // Code not shown.
    }
}

//BankingTransaction is not a data contract class,
//but is an XmlSerializer-compatible class instead.
public class BankingTransaction
{
    [XmlAttribute]
    public string Operation;
    [XmlElement]
    public Account fromAccount;
    [XmlElement]
    public Account toAccount;
    [XmlElement]
    public int amount;
}
//Notice that the Account class must also be XmlSerializer-compatible.
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
    <OperationContract()> _
    Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
        ' Code not shown.
    End Sub
End Class


' BankingTransaction is not a data contract class,
' but is an XmlSerializer-compatible class instead.

Public Class BankingTransaction
    <XmlAttribute()> _
    Public Operation As String
    <XmlElement()> _
    Public fromAccount As Account
    <XmlElement()> _
    Public toAccount As Account
    <XmlElement()> _
    Public amount As Integer
End Class
'Notice that the Account class must also be XmlSerializer-compatible.

安全性考慮

備註

切換串行化引擎時請務必小心。 視所使用的串行化程式而定,相同的類型可以以不同的方式串行化為 XML。 如果您不小心使用錯誤的串行化程式,您可能會從您不打算透露的類型中披露資訊。

例如,只有當數據契約類型被串行化時,類別 DataContractSerializer 才會串行化屬性標示 DataMemberAttribute 的成員。 類別 XmlSerializer 會序列化任何公用成員。 請參閱下列程式代碼中的類型。

[DataContract]
public class Customer
{
    [DataMember]
    public string firstName;
    [DataMember]
    public string lastName;
    public string creditCardNumber;
}
<DataContract()> _
Public Class Customer
    <DataMember()> _
    Public firstName As String
    <DataMember()> _
    Public lastName As String
    Public creditCardNumber As String
End Class

如果類型在服務合約中不經意地選取了 XmlSerializer 類別,則 creditCardNumber 成員會被序列化,這可能並非預期目的。

即使類別 DataContractSerializer 是預設值,您還是可以藉由 DataContractFormatAttribute 將 屬性套用至服務合約類型,明確地為服務選取它(雖然絕對不需要這麼做)。

用於服務的串行化程式是合約不可或缺的一部分,無法藉由選取不同的系結或變更其他組態設定來變更。

其他重要的安全性考慮適用於 類別 XmlSerializer 。 首先,強烈建議任何使用 類別的 XmlSerializer WCF 應用程式都以保護的密鑰進行簽署,以避免洩漏。 這項建議適用於進行手動切換至 XmlSerializer 時,也適用於進行自動切換時(由 Svcutil.exe、添加服務參考或類似工具)。 這是因為 XmlSerializer 串行化引擎支援載入 預先產生的串行化元件 ,只要它們與應用程式簽署的索引鍵相同。 未簽署的應用程式完全無法防範可能出現的安全漏洞,這是因為惡意元件可能匹配到預先產生的序列化元件的預期名稱,並以此放置於應用程式資料夾或全域組件快取中。 當然,攻擊者必須先取得這兩個位置之一的寫入許可權,才能嘗試此動作。

當您使用 XmlSerializer 時,另一個威脅就會存在,它與系統暫存資料夾的寫入許可權相關。 串行化引擎會 XmlSerializer 在此資料夾中建立及使用暫時 串行化元件 。 您應該注意,任何具有暫存資料夾寫入許可權的程式,都可能會以惡意代碼覆寫這些串行化元件。

XmlSerializer 支援的規則

您無法直接將 XmlSerializer 相容的屬性套用至合約作業的參數或傳回值。 不過,它們可以套用至已定義類型的訊息(訊息合約本文部分),如下列程式碼所示。

[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
    [OperationContract]
    public void ProcessTransaction(BankingTransaction bt)
    {
        //Code not shown.
    }
}

[MessageContract]
public class BankingTransaction
{
    [MessageHeader]
    public string Operation;
    [XmlElement, MessageBodyMember]
    public Account fromAccount;
    [XmlElement, MessageBodyMember]
    public Account toAccount;
    [XmlAttribute, MessageBodyMember]
    public int amount;
}
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
    <OperationContract()> _
    Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
        'Code not shown.
    End Sub
End Class

<MessageContract()> _
Public Class BankingTransaction
    <MessageHeader()> _
    Public Operation As String
    <XmlElement(), MessageBodyMember()> _
    Public fromAccount As Account
    <XmlElement(), MessageBodyMember()> _
    Public toAccount As Account
    <XmlAttribute(), MessageBodyMember()> _
    Public amount As Integer
End Class

當套用至具類型的訊息成員時,這些屬性會覆寫與具型別訊息屬性衝突的屬性。 例如,在下列程式代碼中,ElementName 會覆寫 Name

    [MessageContract]
    public class BankingTransaction
    {
        [MessageHeader] public string Operation;

        //This element will be <fromAcct> and not <from>:
        [XmlElement(ElementName="fromAcct"), MessageBodyMember(Name="from")]
        public Account fromAccount;

        [XmlElement, MessageBodyMember]
        public Account toAccount;

        [XmlAttribute, MessageBodyMember]
        public int amount;
}
<MessageContract()> _
Public Class BankingTransaction
    <MessageHeader()> _
    Public Operation As String

    'This element will be <fromAcct> and not <from>:
    <XmlElement(ElementName:="fromAcct"), _
        MessageBodyMember(Name:="from")> _
    Public fromAccount As Account

    <XmlElement(), MessageBodyMember()> _
    Public toAccount As Account

    <XmlAttribute(), MessageBodyMember()> _
    Public amount As Integer
End Class

使用MessageHeaderArrayAttribute時不支援XmlSerializer屬性。

備註

在此情況下,XmlSerializer 會擲回以下例外狀況,這在 WCF 之前釋放:「在 schema 的頂層宣告的元素不能有 maxOccurs> 1。 使用 XmlArrayXmlArrayItem 而非 XmlElementAttribute,或採用 Wrapped 參數樣式,為「more」提供包裹元素。

如果您收到這類例外狀況,請調查此情況是否適用。

WCF 不支援 SoapIncludeAttribute 訊息合約和作業合約中的 和 XmlIncludeAttribute 屬性;請改用 KnownTypeAttribute 屬性。

實作 IXmlSerializable 介面的類型

IXmlSerializable 介面所實現的類型完全受到 DataContractSerializer 的支援。 屬性 XmlSchemaProviderAttribute 應該一律套用至這些類型,以控制其架構。

警告

如果您要串行化多型類型,則必須將 套用 XmlSchemaProviderAttribute 至 類型,以確保已串行化正確的類型。

實作的類型 IXmlSerializable有三個品種:代表任意內容類型的類型、代表單一元素的類型,以及舊版 DataSet 類型。

  • 內容類型使用由屬性XmlSchemaProviderAttribute指定的架構提供者方法。 方法不會傳回 null ,而且 IsAny 屬性上的 屬性會保留在其預設值 false。 這是最常見的IXmlSerializable類型的用法。

  • 元素類型在IXmlSerializable類型必須控制其自身根元素名稱時使用。 若要將類型標示為項目類型,請將 屬性上的 IsAny 屬性設定XmlSchemaProviderAttributetrue 或從架構提供者方法傳回null。 對於項目類型而言,具有架構提供者方法是選擇性的 – 您可以指定 null ,而不是 中的 XmlSchemaProviderAttribute方法名稱。 不過,如果 IsAnytrue 且已指定架構提供者方法,則方法必須傳回 null

  • 舊版 DataSet 類型是未加上 IXmlSerializable 屬性標記的 XmlSchemaProviderAttribute 類型。 相反地,它們會依賴 GetSchema 方法來產生架構。 此模式用於 DataSet 類型,且其具型別數據集會衍生舊版 .NET Framework 中的類別,但現在已過時,而且僅基於舊版的原因才支援。 請勿依賴此模式,且一律將 套用 XmlSchemaProviderAttribute 至您的 IXmlSerializable 類型。

IXmlSerializable 的內容類型

當一個類型的數據成員實作了 IXmlSerializable 並且是先前定義的內容類型時,串行化程序會寫入該數據成員的包裝元素,並將控制權傳遞至 WriteXml 方法。 實施 WriteXml 可以寫入任何 XML,其中包括將屬性加入至包裝元素。 完成WriteXml之後,串行化程式會關閉元素。

當反序列化實作 IXmlSerializable 且作為之前定義的內容類型的數據成員時,反序列化器會將 XML 讀取器定位在數據成員的包裝元素上,並將控制權交給 ReadXml 方法。 方法必須讀取整個元素,包括起始與結束標籤。 請確定您的程式 ReadXml 代碼會處理元素是空的案例。 此外,您的 ReadXml 實作不應依賴包覆元素被命名為特定方式。 串行化程式選擇的名稱可能會有所不同。

可以以多型方式將 IXmlSerializable 內容類型指派給類型為 Object 的數據成員。 也允許類型實例為 Null。 最後,可以使用啟用了物件圖形保留功能的IXmlSerializable型別和NetDataContractSerializer。 所有這些功能都需要 WCF 串行化程式將特定屬性(“nil”和“type”屬性在 XML 架構實例命名空間中,以及“Id”、“Ref”、“Type”和“Assembly”屬性在 WCF 特定命名空間中)附加到包裝元素中。

實作 ReadXml 時要忽略的屬性

在將控制權轉移給您的 ReadXml 程式碼之前,解串行化器會檢查 XML 元素,偵測到這些特殊的 XML 屬性後,會根據它們採取相應的動作。 例如,如果「nil」為true,則會反序列化為空值,而不會呼叫ReadXml。 如果偵測到多型,元素的內容會被反序列化,彷彿它是不同的類型。 呼叫多型指派型別的實現ReadXml。 在任何情況下,ReadXml 的實作都應該忽略這些特殊屬性,因為它們是由解序程序處理。

IXmlSerializable 內容類型的結構考量

匯出架構和 IXmlSerializable 內容類型時,會呼叫架構提供者方法。 一個XmlSchemaSet被傳遞至一個架構提供者方法。 方法可以將任何有效的架構新增至架構集。 架構集包含架構匯出發生時已經知道的架構。 當架構提供者方法必須將項目加入架構集時,它必須判斷具有適當命名空間的 XmlSchema 是否已存在於集合中。 如果這樣做,架構提供者方法必須將新專案新增至現有的 XmlSchema。 否則,它必須建立新的 XmlSchema 實例。 zh-TW: 如果使用 IXmlSerializable 類型的陣列,這是很重要的。 例如,如果您的 IXmlSerializable 類型在命名空間 「B」 中匯出為類型 「A」,則當架構提供者方法呼叫架構集時,可能會包含 「B」 的架構來保存 「ArrayOfA」 類型的架構。

除了將型別加入至 XmlSchemaSet之外,內容類型的架構提供者方法必須傳回非空值。 它可以返回一個 XmlQualifiedName,指示要用於給定 IXmlSerializable 類型的架構類型名稱。 此限定名稱也會做為型別的數據合約名稱和命名空間。 當架構提供者方法傳回時,允許傳回架構集合中不存在的類型。 不過,預期在導出所有相關類型時,在 Export 上呼叫 XsdDataContractExporter 方法並存取 Schemas 屬性,該型別存在於架構集中。 在進行所有相關 Schemas 呼叫之前存取 Export 屬性可能會導致 XmlSchemaException。 如需匯出程式的詳細資訊,請參閱 從類別導出架構

架構提供者方法也可以傳回要使用的 XmlSchemaType。 此類型不一定是匿名的。 如果它是匿名的,則每當IXmlSerializable型別被用作數據成員時,IXmlSerializable型別的架構都會匯出為匿名型別。 此 IXmlSerializable 類型仍然具有數據合約名稱和命名空間。 (此決定方式如 數據合約名稱 中所述,不同之處在於 DataContractAttribute 屬性無法用來自定義名稱。如果它不是匿名的,它必須是 中的 XmlSchemaSet其中一個型別。 此案例等同於將某類型的XmlQualifiedName傳回。

此外,類型的全域元素宣告會被匯出。 如果類型沒有 XmlRootAttribute 套用屬性,元素的名稱和命名空間與資料合約相同,而且其 「nillable」 屬性為 true。 唯一的例外是架構命名空間 (http://www.w3.org/2001/XMLSchema) – 如果型別的數據合約在此命名空間中,對應的全域元素會位於空白命名空間中,因為禁止將新元素加入架構命名空間。 如果類型已應用XmlRootAttribute屬性,則會使用下列屬性匯出全域元素宣告:ElementNameNamespaceIsNullable。 套用預設值 XmlRootAttribute 的情況包括資料合約名稱、空白命名空間,以及「nillable」設定為 true

相同的全域元素宣告規則會套用至舊版數據集類型。 請注意,XmlRootAttribute無法覆寫透過自訂程式代碼新增的全域元素宣告,無論是使用架構提供者方法新增至 XmlSchemaSet ,還是透過舊版資料集類型新增至 GetSchema

IXmlSerializable 項目類型

IXmlSerializable 專案型別的 IsAny 屬性已設定為 true ,或讓其架構提供者方法傳回 null

將項目類型串行化和還原串行化與將內容類型串行化和還原串行化非常相似。 不過,有一些重要的差異:

  • 實作 WriteXml 應該只撰寫一個元素(當然可以包含多個子元素)。 它不應該在單一元素、多個同層級元素或混合內容之外寫入屬性。 元素可能是空的。

  • 實作 ReadXml 不應該讀取包裝元件。 預期會讀取 WriteXml 所產生的一個元素。

  • 定期序列化元素類型時(例如,作為資料合約中的資料成員形式),序列化器會在呼叫 WriteXml 前輸出包裝元素,就像內容類型一樣。 不過,在最上層串行化元素類型時,串行化器通常不會在WriteXml寫入的元素周圍輸出一個包裝元素,除非在DataContractSerializerNetDataContractSerializer建構函式中建構串行化器時明確指定根名稱和命名空間。 如需詳細資訊,請參閱 串行化和還原串行化

  • 在最上層串行化元素類型時,如果在建構階段未指定根名稱和命名空間,WriteStartObjectWriteEndObject 基本上不會執行任何操作,WriteObjectContent 則會呼叫 WriteXml。 在此模式中,待串行化的物件不能是 null,且不能以多型方式指派。 此外,無法啟用物件圖形保留,而且 NetDataContractSerializer 無法使用 。

  • 在反序列化最上層的元素類型時,如果未在建構階段指定根名稱和命名空間,IsStartObject 就會返回 true,前提是它能找到任何元素的開頭。 ReadObject 的行為方式在參數 verifyObjectName 設定為 true 時,與 IsStartObject 實際讀取物件之前相同。 ReadObject 然後將控件傳遞至 ReadXml 方法。

針對元素型別導出的架構與上一節中描述的 XmlElement 型別相同,不同之處在於架構提供者方法可以像針對內容類型一樣將任何其他架構新增至 XmlSchemaSetXmlRootAttribute 屬性不允許搭配元素類型使用,且永遠不會針對這些類型發出全域元素宣告。

與 XmlSerializer 的不同之處

介面 IXmlSerializableXmlSchemaProviderAttributeXmlRootAttribute 屬性也由 XmlSerializer 瞭解。 不過,在數據合約模型中,這些處理方式有一些差異。 下列清單中摘要說明重要的差異:

  • 架構提供者方法必須是公用的 ,才能用於 XmlSerializer,但不必是公用,才能用於數據合約模型中。

  • IsAny 位於 true 數據合約模型中,但未使用 XmlSerializer時,會呼叫架構提供者方法。

  • 當內容或舊版數據集類型沒有XmlRootAttribute屬性時,XmlSerializer會在空白命名空間中匯出全域元素宣告。 在數據合約模型中,使用的命名空間通常是數據合約命名空間,如先前所述。

在建立與這兩種串行化技術搭配使用的型別時,請注意這些差異。

匯入 IXmlSerializable 架構

IXmlSerializable 類型生成的架構匯入時,會有幾個可能性:

  • 產生的架構可能是有效的數據合約架構,如 數據合約架構參考中所述。 在此情況下,可以如往常匯入架構,併產生一般數據合約類型。

  • 產生的架構可能不是有效的數據合約架構。 例如,您的架構提供者方法可能會產生包含數據合約模型中不支援之 XML 屬性的架構。 在此情況下,您可以將架構匯入為 IXmlSerializable 類型。 預設不會開啟此匯入模式,但很容易啟用 –例如, /importXmlTypes 使用命令行參數切換至 ServiceModel 元數據公用程式工具 (Svcutil.exe) 。 這在匯入模式以產生類別中詳細說明。 請注意,您必須直接使用類型實例的 XML。 您也可以考慮使用不同的串行化技術,以支援更廣泛的架構 – 請參閱使用 XmlSerializer主題。

  • 您可能想要在 Proxy 中重複使用現有的 IXmlSerializable 類型,而不是產生新的類型。 在此情況下,匯入架構至產生類型主題中所述的參考型別功能可用來指出要重複使用的類型。 這對應於在 /reference svcutil.exe上使用 開關,該開關會指定包含可再次使用之類型的組件。

XmlSerializer 舊版系統行為

在 .NET Framework 4.0 和更早版本中,XmlSerializer 會藉由將 C# 程式代碼寫入檔案來產生暫時串行化元件。 檔案然後編譯成組件。 此行為有一些不受歡迎的後果,例如減緩串行化程式的啟動時間。 在 .NET Framework 4.5 中,此行為已變更為產生元件,而不需要使用編譯程式。 有些開發人員可能會想要查看產生的 C# 程式代碼。 您可以透過下列設定來指定使用此舊版行為:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.xml.serialization>
    <xmlSerializer tempFilesLocation='e:\temp\XmlSerializerBug' useLegacySerializerGeneration="true" />
  </system.xml.serialization>
  <system.diagnostics>
    <switches>
      <add name="XmlSerialization.Compilation" value="1" />
    </switches>
  </system.diagnostics>
</configuration>

如果您遇到相容性問題,例如 XmlSerializer 無法序列化具有非公用新覆寫的衍生類別,您可以藉由使用下列設定切換回 XMLSerializer 舊版行為:

<configuration>
  <appSettings>
    <add key="System:Xml:Serialization:UseLegacySerializerGeneration" value="true" />
  </appSettings>
</configuration>

除了上述組態之外,您可以在執行 .NET Framework 4.5 或更新版本的計算機上使用下列設定:

<configuration>
  <system.xml.serialization>
    <xmlSerializer useLegacySerializerGeneration="true"/>
  </system.xml.serialization>
</configuration>

備註

此切換 <xmlSerializer useLegacySerializerGeneration="true"/> 僅適用於執行 .NET Framework 4.5 或更新版本的機器。 上述 appSettings 方法適用於所有 .NET Framework 版本。

另請參閱