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 字串參數是用來描述要求訊息,而 float 傳回值則用來描述回復訊息。 如果單獨傳回值不足以描述回復訊息,可以使用 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、 stringfloat和 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) 描述服務時,兩個常用的樣式是 Document 和 remote procedure call (RPC)。 在 [文件] 樣式中,會使用架構描述整個訊息本文,而 WSDL 會參考該架構內的元素來描述各種訊息本文部分。 在 RPC 樣式中,WSDL 是指每個訊息部分的架構類型,而不是元素。 在某些情況下,您必須手動選取其中一種樣式。 您可以套用 DataContractFormatAttribute 屬性並設定 Style 屬性(使用時DataContractSerializer),或在 屬性上Style設定 XmlSerializerFormatAttribute (使用 XmlSerializer時)。
此外,支援 XmlSerializer 兩種形式的序列化 XML:Literal 和 Encoded。
Literal 是最常接受的表單,而且是唯一支援的表單 DataContractSerializer 。
Encoded 是SOAP規格第5節中所述的舊版窗體,不建議用於新服務。 若要切換至 Encoded 模式,請將 Use 屬性上的 XmlSerializerFormatAttribute 屬性設定為 Encoded。
在大部分情況下,您不應該變更 Style 和 Use 屬性的預設設定。
控制串行化進程
您可以採取多種方式來自定義資料序列化的方式。
變更伺服器串行化設定
使用預設值 DataContractSerializer 時,您可以在服務中套用 ServiceBehaviorAttribute 屬性,來控制序列化過程的一些層面。 具體而言,您可以使用 MaxItemsInObjectGraph 屬性來設定限制還原串行化物件 DataContractSerializer 數目上限的配額。 您可以使用 IgnoreExtensionDataObject 屬性來關閉來回版本控制功能。 如需配額的詳細資訊,請參閱 數據的安全性考慮。 如需有關來回處理的詳細資訊,請參閱 Forward-Compatible 數據合約。
[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 類型為 Address 的欄位,稱為 billTo 和 shipTo。 如果這兩個字段都設定為相同的 Address 實例,則串行化和還原串行化之後有兩個相同的 Address 實例。 這是因為沒有標準可互通的方式表示 XML 中的物件圖形(除了在XmlSerializer上可用的舊版 SOAP 編碼標準,如在上一節Style和Use中所述)。 將物件圖形串行化為樹狀結構有某些缺點,例如,具有循環參考的圖形無法串行化。 有時候,即使無法互通,還是必須切換到真正的物件圖形串行化。 這可以透過使用建構 DataContractSerializer,並將 preserveObjectReferences 參數設定為 true 來完成。
有時候,內建串行化程式不足以用於您的案例。 在大部分情況下,您仍然可以使用 XmlObjectSerializer 抽象概念,DataContractSerializer 和 NetDataContractSerializer 都是由其衍生的。
前三個案例(.NET 類型保留、物件圖形保留和完全自定義 XmlObjectSerializer型串行化)都需要插入自定義串行化程式。 若要這樣做,請執行下列步驟:
撰寫衍生自 DataContractSerializerOperationBehavior 的行為定义。
覆寫這兩個
CreateSerializer方法,以傳回您自己的序列化程式(可以使用NetDataContractSerializer、設置為DataContractSerializer的preserveObjectReferencestrue、或您自己的自定義XmlObjectSerializer)。開啟服務主機或建立用戶端通道之前,請先移除現有的 DataContractSerializerOperationBehavior 行為,並插入您在先前步驟中建立的自定義衍生類別。
如需進階串行化概念的詳細資訊,請參閱 串行化和還原串行化。