Windows Communication Foundation(WCF)可以使用两种不同的序列化技术将应用程序中的数据转换为在客户端和服务之间传输的 XML: DataContractSerializer
和 XmlSerializer
。
DataContractSerializer
默认情况下,WCF 使用 DataContractSerializer 类序列化数据类型。 此序列化程序支持以下类型:
基元类型(例如整数、字符串和字节数组),以及一些特殊类型,如 XmlElement 和 DateTime,它们被视为基元。
数据协定类型(用 DataContractAttribute 属性标记的类型)。
用 SerializableAttribute 属性标记的类型,其中包括实现 ISerializable 接口的类型。
实现 IXmlSerializable 接口的类型。
许多常见集合类型,其中包括许多泛型集合类型。
许多 .NET Framework 类型属于后两个类别,因此可序列化。 可序列化类型的数组也是可序列化的。 有关完整列表,请参阅 “在服务协定中指定数据传输”。
与 DataContractSerializer数据协定类型一起使用是编写新 WCF 服务的建议方法。 有关详细信息,请参阅 使用数据协定。
XmlSerializer
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 之前引发的:“架构中顶层声明的元素不能有 maxOccurs
> 1。” 使用 XmlArray
或 XmlArrayItem
而不是 XmlElementAttribute
,或使用换行的参数样式为“more”提供包装元素。”
如果您接收到此异常,请调查是否属于这种情况。
WCF 不支持消息协定和操作协定中的SoapIncludeAttribute与XmlIncludeAttribute属性;请改用KnownTypeAttribute属性。
实现 IXmlSerializable 接口的类型
实现 IXmlSerializable
接口的类型得到 DataContractSerializer
的充分支持。 属性 XmlSchemaProviderAttribute 应始终应用于这些类型来控制其架构。
警告
如果要序列化多态类型,必须在该类型上应用 XmlSchemaProviderAttribute,以确保正确序列化此类型。
有三种实现的类型 IXmlSerializable
:表示任意内容类型的类型、表示单个元素的类型和旧 DataSet 类型。
内容类型使用
XmlSchemaProviderAttribute
属性所指定的架构提供程序方法。 该方法不返回null
,并且 IsAny 属性保持其默认值false
。 这是这些IXmlSerializable
类型最常见的用法。元素类型用于当
IXmlSerializable
类型必须控制其自己的根元素名称时。 若要将类型标记为元素类型,可以在IsAny属性上设置XmlSchemaProviderAttribute属性至true
,或者从架构提供程序方法返回null
。 对于元素类型,使用架构提供程序方法是可选的 – 可以在null
中指定XmlSchemaProviderAttribute
,而不是方法名称。 但是,如果IsAny
被指定为true
且指定了架构提供程序方法,则该方法必须返回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 序列化程序将某些属性附加到 XML 架构实例命名空间中的包装元素(XML 架构实例命名空间中的“nil”和“type”,以及 WCF 特定命名空间中的“Id”、“Ref”、“Type”和“Assembly”)。
实现 ReadXml 时要忽略的属性
在将执行控制权交给您的 ReadXml
代码之前,反序列化程序会检查 XML 元素,检测这些特殊的 XML 属性,并对其进行处理。 例如,如果“nil”为true
,则反序列化后生成一个 null 值,并且不会调用ReadXml
。 如果检测到多态性,则元素的内容将反序列化,就好像它是另一种类型一样。 调用以多元方式分配的类型的 ReadXml
实现。 在任何情况下, ReadXml
实现都应忽略这些特殊属性,因为它们由反序列化程序处理。
IXmlSerializable 内容类型的架构注意事项
导出架构和 IXmlSerializable
内容类型时,将调用架构提供程序方法。 并将 XmlSchemaSet 传递给架构提供程序方法。 该方法可以将任何有效的架构添加到架构集。 架构集包含架构导出时已知道的架构。 当架构提供程序方法必须将项添加到架构集时,它必须确定具有相应命名空间的项 XmlSchema 是否已存在于该集中。 如果这样做,架构提供程序方法必须将新项添加到现有 XmlSchema
项。 否则,它必须创建新 XmlSchema
实例。 这在使用IXmlSerializable
类型的数组时非常重要。 例如,如果在命名空间“B”中有一个类型“IXmlSerializable
”被导出为类型“A”,那么当调用架构提供程序方法时,架构集可能已经包含“B”的架构,以容纳“ArrayOfA”类型。
除了向 XmlSchemaSet添加内容类型之外,内容类型的架构提供程序方法还必须返回非 null 值。 它可能返回 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
属性,则使用以下属性导出全局元素声明: ElementNameNamespace 和IsNullable属性。 应用了 XmlRootAttribute
的默认值是数据协定名称、空命名空间和为 true
的“nillable”。
相同的全局元素声明规则适用于旧数据集类型。 请注意,XmlRootAttribute
无法重写通过自定义代码添加的全局元素声明,无论是使用构架提供程序方法添加到 XmlSchemaSet
中的,还是通过旧数据集类型的 GetSchema
添加的。
IXmlSerializable 元素类型
IXmlSerializable
元素类型要么将 IsAny
属性设置为 true
,要么其架构提供程序方法返回 null
。
序列化和反序列化元素类型与序列化和反序列化内容类型非常相似。 但是,存在一些重要差异:
实现
WriteXml
应只编写一个元素(当然可以包含多个子元素)。 它不应在此单个元素、多个同级元素或混合内容之外编写属性。 元素可能为空。ReadXml
实现不应读取包装元素。 它应读取WriteXml
生成的那一个元素。当定期序列化元素类型(例如,作为数据协定中的数据成员)时,序列化程序会在调用
WriteXml
之前输出包装元素,就像内容类型一样。 但是,在顶层序列化元素类型时,序列化程序通常根本不会在WriteXml
所写的元素周围输出任何包装元素,除非在DataContractSerializer
或NetDataContractSerializer
构造函数中显式指定了根名称和命名空间。 有关详细信息,请参阅 序列化和反序列化。在顶层序列化元素类型时,若未在构造时指定根名称和命名空间,WriteStartObject和WriteEndObject基本上不执行任何操作,而WriteObjectContent则调用
WriteXml
。 在此模式下,要序列化的对象不能是null
,也不能进行多态分配。 此外,对象图保留无法启用,且NetDataContractSerializer
无法使用。在顶层反序列化元素类型时,如果在构造时没有指定根名称和命名空间,并且能够找到任何元素的开头,则 IsStartObject 会返回
true
。 ReadObject 参数verifyObjectName
设置为true
的行为方式与IsStartObject
实际读取对象之前的行为方式相同。ReadObject
然后将控制权传递给ReadXml
方法。
为元素类型导出的架构与前面部分所述的 XmlElement
类型相同,只是架构提供程序方法可以像对待内容类型一样,向 XmlSchemaSet 添加任何其他架构。 不允许将 XmlRootAttribute
属性与元素类型一起使用,并且永远不会针对这些类型发出全局元素声明。
与 XmlSerializer 的差异
IXmlSerializable
接口和XmlSchemaProviderAttribute
、XmlRootAttribute
属性也被XmlSerializer理解。 但是,在数据协定模型中对这些方法的处理方式存在一些差异。 以下列表中汇总了重要差异:
架构提供程序方法必须是公共方法才能在
XmlSerializer
中使用,但在数据合同模型中则不必是公共方法。如果在数据协定模型中
IsAny
为true
但是不具有XmlSerializer
,则调用架构提供程序方法。当内容或旧数据集类型不存在该
XmlRootAttribute
属性时,导出XmlSerializer
空白命名空间中的全局元素声明。 在数据协定模型中,使用的命名空间通常为数据协定命名空间,如前所述。
在创建与两种序列化技术一起使用的类型时,请注意这些差异。
导入 IXmlSerializable 架构
导入从 IXmlSerializable
类型生成的架构时,有一些可能性:
生成的架构可以是有效的数据协定架构,如 数据协定架构参考中所述。 在这种情况下,可以像往常一样导入架构,并生成常规数据协定类型。
生成的架构可能不是有效的数据协定架构。 例如,架构提供程序方法可能会生成涉及数据协定模型中不支持的 XML 属性的架构。 在这种情况下,可以将架构导入为
IXmlSerializable
类型。 默认情况下,此导入模式未启用,但可以轻松启用 -例如,使用命令行切换到 ServiceModel 元数据实用工具工具(Svcutil.exe)。> 在 导入架构以生成类中详细介绍了这一点。 请注意,您必须直接处理您的类型实例的 XML。 还可以考虑使用不同的支持更广泛的架构的序列化技术 - 请参阅有关使用 XmlSerializer
的主题。你可能想要重用代理中的现有
IXmlSerializable
类型,而不是生成新类型。 在这种情况下,导入架构到生成类型主题中所述的引用类型功能可用于指示要重用的类型。 这对应于在 svcutil.exe上使用/reference
选项,该选项指定包含要重用类型的程序集。
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 版本。