Windows Communication Foundation (WCF)可以视为消息传递基础结构。 服务操作可以接收消息、处理消息并发送消息。 消息是使用操作协定描述的。 例如,请考虑以下协定。
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(string fromCity, string toCity);
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
End Interface
在此,GetAirfare
操作接受包含fromCity
和toCity
信息的消息,然后返回一个包含数字的消息。
本主题解释了操作合同可以用来描述消息的各种方式。
使用参数描述消息
描述消息的最简单方法是使用参数列表和返回值。 在前面的示例中,fromCity
toCity
使用了字符串参数来描述请求消息,而浮点返回值用于描述回复消息。 如果仅返回值不足以描述回复消息,可以使用 out 参数。 例如,以下操作在其请求消息中含有 fromCity
和 toCity
,而其答复消息中包含一个数字和货币:
[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
此外,您可以使用引用参数使参数成为请求和答复消息的一部分。 参数必须是可以序列化的类型(转换为 XML)。 默认情况下,WCF 使用名为类的 DataContractSerializer 组件来执行此转换。 支持大多数基元类型(例如int
,string
和 float
DateTime
.)。 用户定义的类型通常必须具有数据协定。 有关详细信息,请参阅 使用数据协定。
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(Itinerary itinerary, DateTime date);
[DataContract]
public class Itinerary
{
[DataMember]
public string fromCity;
[DataMember]
public string toCity;
}
}
Public Interface IAirfareQuoteService
<OperationContract()>
GetAirfare(itinerary as Itinerary, date as DateTime) as Double
<DataContract()>
Class Itinerary
<DataMember()>
Public fromCity As String
<DataMember()>
Public toCity As String
End Class
End Interface
有时,DataContractSerializer
无法充分序列化您的类型。 WCF 支持替代序列化引擎, XmlSerializer该引擎还可用于序列化参数。 通过使用XmlSerializer,您可以使用诸如XmlAttributeAttribute
之类的属性来对生成的XML进行更多控制。 若要切换到对特定作或整个服务使用 XmlSerializer ,请将 XmlSerializerFormatAttribute 属性应用于作或服务。 例如:
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
[XmlSerializerFormat]
float GetAirfare(Itinerary itinerary, DateTime date);
}
public class Itinerary
{
public string fromCity;
public string toCity;
[XmlAttribute]
public bool isFirstClass;
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
<XmlSerializerFormat>
GetAirfare(itinerary as Itinerary, date as DateTime) as Double
End Interface
Class Itinerary
Public fromCity As String
Public toCity As String
<XmlSerializerFormat()>
Public isFirstClass As Boolean
End Class
有关详细信息,请参阅 使用 XmlSerializer 类。 请记住,最好不要如此处所示手动切换到 XmlSerializer,除非有该主题详细讨论的特定原因需要执行此操作。
若要将 .NET 参数名称与协定名称隔离开来,可以使用 MessageParameterAttribute 属性来实现此目的,并可以使用 Name
属性来设置协定名称。 例如,以下作协定等效于本主题中的第一个示例。
[OperationContract]
public float GetAirfare(
[MessageParameter(Name="fromCity")] string originCity,
[MessageParameter(Name="toCity")] string destinationCity);
<OperationContract()>
Function GetAirfare(<MessageParameter(Name := "fromCity")> fromCity As String, <MessageParameter(Name := "toCity")> toCity As String) As Double
描述空消息
如果没有输入或引用参数,可以描述空请求消息。 例如,在 C# 中:
[OperationContract]
public int GetCurrentTemperature();
例如,在 Visual Basic 中:
<OperationContract()>
Function GetCurrentTemperature() as Integer
可以通过具有 void
返回类型且没有输出或引用参数来描述空回复消息。 例如,在:
[OperationContract]
public void SetTemperature(int temperature);
<OperationContract()>
Sub SetTemperature(temperature As Integer)
这不同于单向操作,例如:
[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);
<OperationContract(IsOneWay:=True)>
Sub SetLightbulbStatus(isOne As Boolean)
该SetTemperatureStatus
操作返回一条空消息。 如果处理输入消息时出现问题,可能会返回错误。 该 SetLightbulbStatus
操作不返回任何值。 无法在此操作中传递错误条件。
使用消息协定来描述消息
你可能想要使用单个类型来表示整个消息。 尽管可以为此使用数据协定,但推荐的方式是使用消息协定,这样可以避免在生成的 XML 中出现不必要的包装层次。 此外,消息协定允许你更好地控制生成的消息。 例如,你可以确定哪些信息应位于消息正文中,哪些信息应位于消息标头中。 以下示例显示了消息协定的使用。
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
GetAirfareResponse GetAirfare(GetAirfareRequest request);
}
[MessageContract]
public class GetAirfareRequest
{
[MessageHeader] public DateTime date;
[MessageBodyMember] public Itinerary itinerary;
}
[MessageContract]
public class GetAirfareResponse
{
[MessageBodyMember] public float airfare;
[MessageBodyMember] public string currency;
}
[DataContract]
public class Itinerary
{
[DataMember] public string fromCity;
[DataMember] public string toCity;
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
Function GetAirfare(request As GetAirfareRequest) As GetAirfareResponse
End Interface
<MessageContract()>
Public Class GetAirfareRequest
<MessageHeader()>
Public Property date as DateTime
<MessageBodyMember()>
Public Property itinerary As Itinerary
End Class
<MessageContract()>
Public Class GetAirfareResponse
<MessageBodyMember()>
Public Property airfare As Double
<MessageBodyMember()> Public Property currency As String
End Class
<DataContract()>
Public Class Itinerary
<DataMember()> Public Property fromCity As String
<DataMember()> Public Property toCity As String
End Class
有关详细信息,请参阅 使用消息协定。
在前面的示例中, DataContractSerializer 该类默认仍使用。 该 XmlSerializer 类还可用于消息协定。 为此,请将 XmlSerializerFormatAttribute 属性应用于操作或合约,并在消息标头和正文成员中使用与 XmlSerializer 类兼容的类型。
使用流描述消息
在操作中描述消息的另一种方法是,在操作协定中使用Stream类或其派生类之一,或者作为消息协定正文中的唯一成员。 对于传入消息,类型必须为 Stream
- 不能使用派生类。
WCF 不调用序列化程序,而是从流中检索数据,并将其直接放入传出消息中,或者从传入消息中检索数据,并将其直接放入流中。 下面的示例演示了流的使用。
[OperationContract]
public Stream DownloadFile(string fileName);
<OperationContract()>
Function DownloadFile(fileName As String) As String
不能在单个消息正文中合并 Stream
和非流数据。 使用消息协定将额外数据放入消息标头中。 在定义操作合同时,以下示例显示了流的不正确用法。
//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);
'Incorrect:
'<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
以下示例显示了定义操作契约时正确使用流的方式。
[OperationContract]
public void UploadFile (UploadFileMessage message);
//code omitted
[MessageContract]
public class UploadFileMessage
{
[MessageHeader] public string fileName;
[MessageBodyMember] public Stream fileData;
}
<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
'Code Omitted
<MessageContract()>
Public Class UploadFileMessage
<MessageHeader()>
Public Property fileName As String
<MessageBodyMember()>
Public Property fileData As Stream
End Class
有关详细信息,请参阅 大型数据和流式处理。
使用 Message 类
若要完全以编程方式控制发送或接收的消息,可以直接使用该 Message 类,如以下示例代码所示。
[OperationContract]
public void LogMessage(Message m);
<OperationContract()>
Sub LogMessage(m As Message)
这是一种高级方案,在 “使用消息类”中对此进行了详细介绍。
描述错误消息
除了返回值和输出或引用参数描述的消息外,任何非单向作都至少可以返回两条可能的消息:其正常响应消息和错误消息。 请考虑下面的操作协定。
[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)
此作可能返回包含 float
数字的普通消息,或者返回包含错误代码和说明的错误消息。 可以通过在服务实现中抛出 FaultException 来实现此目的。
可以使用特性 FaultContractAttribute 指定其他可能的错误消息。 附加故障必须使用 DataContractSerializer 进行序列化,如以下示例代码所示。
[OperationContract]
[FaultContract(typeof(ItineraryNotAvailableFault))]
float GetAirfare(string fromCity, string toCity, DateTime date);
//code omitted
[DataContract]
public class ItineraryNotAvailableFault
{
[DataMember]
public bool IsAlternativeDateAvailable;
[DataMember]
public DateTime alternativeSuggestedDate;
}
<OperationContract()>
<FaultContract(GetType(ItineraryNotAvailableFault))>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime) As Double
'Code Omitted
<DataContract()>
Public Class
<DataMember()>
Public Property IsAlternativeDateAvailable As Boolean
<DataMember()>
Public Property alternativeSuggestedDate As DateTime
End Class
可以通过引发相应数据协定类型的 FaultException<TDetail> 来生成这些其他错误。 有关详细信息,请参阅 处理异常和错误。
不能使用该 XmlSerializer 类来描述错误。 XmlSerializerFormatAttribute 对错误协定无效。
使用派生类型
你可能希望在操作或消息合同中使用基类型,然后在实际调用操作时使用派生类型。 在这种情况下,必须使用 ServiceKnownTypeAttribute 属性或某种替代机制来允许使用派生类型。 请考虑以下操作。
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
假设有两种类型, Book
并且 Magazine
派生自 LibraryItem
。 若要在 IsLibraryItemAvailable
操作中使用这些类型,可以按如下所示更改操作:
[OperationContract]
[ServiceKnownType(typeof(Book))]
[ServiceKnownType(typeof(Magazine))]
public bool IsLibraryItemAvailable(LibraryItem item);
或者,当默认值KnownTypeAttribute正在使用时,可以使用DataContractSerializer该属性,如以下示例代码所示。
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
// code omitted
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryItem
{
//code omitted
}
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
'Code Omitted
<DataContract()>
<KnownType(GetType(Book))>
<KnownType(GetType(Magazine))>
Public Class LibraryItem
'Code Omitted
End Class
在使用XmlIncludeAttribute时,可以使用XmlSerializer属性。
可以将ServiceKnownTypeAttribute属性应用于操作或整个服务。 它接受类型或方法名称,以调用获得已知类型的列表,就像 KnownTypeAttribute 属性一样。 有关详细信息,请参阅 数据协定已知类型。
指定使用方法和样式
使用 Web 服务描述语言(WSDL)描述服务时,两种常用样式是文档和远程过程调用(RPC)。 在“文档”样式中,使用架构描述整个消息正文,WSDL 通过引用该架构中的元素来描述各种消息正文部分。 在 RPC 样式中,WSDL 引用每个消息部件的架构类型,而不是元素。 在某些情况下,必须手动选择其中一种样式。 若要执行此操作,可以应用 DataContractFormatAttribute 属性并设置 Style
属性 (Property)(使用 DataContractSerializer 时),或者设置 Style
属性 (Attribute) 上的 XmlSerializerFormatAttribute(使用 XmlSerializer 时)。
此外,XmlSerializer 支持两种形式的序列化 XML:Literal
和 Encoded
。
Literal
是最常接受的表单,也是唯一支持的形式 DataContractSerializer 。
Encoded
是 SOAP 规范第 5 节中所述的旧窗体,不建议用于新服务。 若要切换到Encoded
模式,请将Use
XmlSerializerFormatAttribute属性上的属性设置为 Encoded
。
在大多数情况下,您不应更改Style
和 Use
属性的默认设置。
控制序列化过程
可以执行许多操作来自定义序列化数据的方式。
更改服务器序列化设置
使用默认值 DataContractSerializer 时,可以通过将属性应用于 ServiceBehaviorAttribute 服务来控制服务上的序列化过程的一些方面。 具体而言,可以使用 MaxItemsInObjectGraph
属性来设置限制 DataContractSerializer 反序列化的最大对象数的配额。 可以使用 IgnoreExtensionDataObject
属性关闭往返版本控制的特性。 有关配额的详细信息,请参阅 数据的安全注意事项。 有关往返的详细信息,请参阅向前兼容的数据协定。
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public class MyDataService:IDataService
{
public DataPoint[] GetData()
{
// Implementation omitted
}
}
<ServiceBehavior(MaxItemsInObjectGraph:=100000)>
Public Class MyDataService Implements IDataService
Function GetData() As DataPoint()
‘ Implementation omitted
End Function
End Interface
序列化行为
WCF 中提供了两种行为:DataContractSerializerOperationBehavior 和 XmlSerializerOperationBehavior,它们根据为特定操作使用的序列化程序自动插入。 由于这些行为会自动应用,因此通常不必注意这些行为。
但是,DataContractSerializerOperationBehavior
具有MaxItemsInObjectGraph
、IgnoreExtensionDataObject
和DataContractSurrogate
等属性,可以用于定制序列化过程。 前两个属性的含义与上一节中所述的含义相同。 可以使用该 DataContractSurrogate
属性启用数据协定代理项,这是用于自定义和扩展序列化过程的强大机制。 有关详细信息,请参阅 数据契约代理。
可以使用DataContractSerializerOperationBehavior
自定义客户端和服务器的序列化。 以下示例演示如何增加 MaxItemsInObjectGraph
客户端上的配额。
ChannelFactory<IDataService> factory = new ChannelFactory<IDataService>(binding, address);
foreach (OperationDescription op in factory.Endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior =
op.Behaviors.Find<DataContractSerializerOperationBehavior>()
as DataContractSerializerOperationBehavior;
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = 100000;
}
}
IDataService client = factory.CreateChannel();
Dim factory As ChannelFactory(Of IDataService) = New ChannelFactory(Of IDataService)(binding, address)
For Each op As OperationDescription In factory.Endpoint.Contract.Operations
Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()
If dataContractBehavior IsNot Nothing Then
dataContractBehavior.MaxItemsInObjectGraph = 100000
End If
Next
Dim client As IDataService = factory.CreateChannel
下面是服务上的等效代码,在自托管的情况下:
ServiceHost serviceHost = new ServiceHost(typeof(IDataService))
foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)
{
foreach (OperationDescription op in ep.Contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior =
op.Behaviors.Find<DataContractSerializerOperationBehavior>()
as DataContractSerializerOperationBehavior;
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = 100000;
}
}
}
serviceHost.Open();
Dim serviceHost As ServiceHost = New ServiceHost(GetType(IDataService))
For Each ep As ServiceEndpoint In serviceHost.Description.Endpoints
For Each op As OperationDescription In ep.Contract.Operations
Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()
If dataContractBehavior IsNot Nothing Then
dataContractBehavior.MaxItemsInObjectGraph = 100000
End If
Next
Next
serviceHost.Open()
在 Web 托管的情况下,必须创建新的 ServiceHost
派生类,并使用服务主机工厂将其插入。
在配置中控制序列化设置
使用 MaxItemsInObjectGraph
终结点或服务行为,可以通过配置来控制 IgnoreExtensionDataObject
和 dataContractSerializer
,如下面的示例所示。
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="LargeQuotaBehavior">
<dataContractSerializer
maxItemsInObjectGraph="100000" />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://example.com/myservice"
behaviorConfiguration="LargeQuotaBehavior"
binding="basicHttpBinding" bindingConfiguration=""
contract="IDataService"
name="" />
</client>
</system.serviceModel>
</configuration>
共享类型序列化、对象图保留和自定义序列化器
使用 DataContractSerializer 数据协定名称而不是 .NET 类型名称进行序列化。 这与面向服务的体系结构原则一致,并且具有极大的灵活性-.NET 类型可以更改,而不会影响线路协定。 在极少数情况下,你可能希望序列化实际的 .NET 类型名称,从而在客户端和服务器之间引入紧密耦合,类似于 .NET Framework 远程处理技术。 建议不要采取这种做法,除非在极少数情况下 — 这些情况通常发生在从 .NET Framework 远程处理迁移到 WCF 的过程中。 在这种情况下,必须使用 NetDataContractSerializer 类而不是 DataContractSerializer 类。
通常将 DataContractSerializer 对象图序列化为对象树。 也就是说,如果多次引用同一对象,则会多次序列化它。 例如,请考虑一个PurchaseOrder
实例,它有两个类型为“地址”的字段,分别称为billTo
和shipTo
。 如果两个字段都设置为同一地址实例,则序列化和反序列化后有两个相同的地址实例。 这样做是因为没有标准的互操作方式来在 XML 中表示对象图(除了前一部分XmlSerializer和Style
中提到的可用在Use
上的旧版 SOAP 编码标准)。 将对象图序列化为树具有某些缺点,例如,无法序列化具有循环引用的图形。 有时,必须切换到真正的对象图序列化,即使它不可互作。 这可以通过使用 DataContractSerializer,将 preserveObjectReferences
参数设置为 true
来完成。
有时,内置序列化程序不足以满足你的方案。 在大多数情况下,你仍然可以使用XmlObjectSerializer这个抽象,它是DataContractSerializer和NetDataContractSerializer的派生来源。
前面的三种情况(.NET 类型保留、对象图保存和完全基于自定义 XmlObjectSerializer
的序列化)都需要插入自定义序列化程序。 为此,请执行下列步骤:
编写自己的派生自 DataContractSerializerOperationBehavior 的行为。
重写两个
CreateSerializer
方法以返回自己的序列化程序(NetDataContractSerializer、将 DataContractSerializer 设置为preserveObjectReferences
的true
或自己的自定义 XmlObjectSerializer)。在打开服务主机或创建客户端通道之前,请删除现有 DataContractSerializerOperationBehavior 行为,并插入在前面的步骤中创建的自定义派生类。
有关高级序列化概念的详细信息,请参阅 序列化和反序列化。
另请参阅
- 使用 XmlSerializer 类
- 如何启用流媒体
- 如何:为类或结构创建基本数据协定