Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
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
, 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 использовать более контроль над результирующий XML с помощью таких атрибутов, как XmlAttributeAttribute
. Чтобы переключиться на использование 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 класс, как показано в следующем примере кода.
[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 атрибут. Дополнительные сведения см. в разделе "Известные типы контракта данных".
Указание использования и стиля
При описании служб с помощью языка описания веб-служб (WSDL) используются два часто используемых стиля — вызов документов и удаленных процедур (RPC). В стиле документа весь текст сообщения описывается с помощью схемы, а WSDL описывает различные части текста сообщения, ссылаясь на элементы в этой схеме. В стиле RPC WSDL относится к типу схемы для каждой части сообщения, а не к элементу. В некоторых случаях необходимо вручную выбрать один из этих стилей. Это можно сделать, применив DataContractFormatAttribute атрибут и задав Style
свойство (при DataContractSerializer использовании) или установив Style
для XmlSerializerFormatAttribute атрибута (при использовании XmlSerializer).
Кроме того, XmlSerializer поддерживает две формы сериализованного XML: Literal
и Encoded
.
Literal
является наиболее распространенной формой и является единственной формой, поддерживающей DataContractSerializer .
Encoded
является устаревшей формой, описанной в разделе 5 спецификации SOAP, и не рекомендуется для новых служб. Чтобы переключиться в 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()
В случае с размещением в Интернете необходимо создать новый производный 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. Это не рекомендуется, за исключением редких случаев, которые обычно происходят при миграции на WCF из удалённого взаимодействия .NET Framework. В этом случае необходимо использовать класс NetDataContractSerializer вместо класса DataContractSerializer.
Обычно DataContractSerializer сериализует граф объектов в виде дерева объектов. То есть, если на один и тот же объект ссылаются более одного раза, он сериализуется несколько раз. Например, рассмотрим экземпляр PurchaseOrder
, который имеет два поля типа Address, называемые billTo
и shipTo
. Если оба поля заданы к одному экземпляру Address, то после сериализации и десериализации существуют два одинаковых экземпляра Address. Это делается из-за отсутствия стандартного взаимодействующего способа для представления графов объектов в XML (за исключением устаревшего стандарта кодировки SOAP, доступного на XmlSerializer, как описано в предыдущем разделе о Style
и Use
). Сериализация графов объектов в виде деревьев имеет определенные недостатки, например, графы с циклическими ссылками не могут быть сериализованы. Иногда необходимо переключиться на истинную сериализацию графа объектов, даже если она не совместима. Это можно сделать, используя DataContractSerializer, сконструированный с параметром preserveObjectReferences
установленным на true
.
Иногда встроенные сериализаторы недостаточны для ваших нужд. В большинстве случаев можно по-прежнему использовать абстракцию XmlObjectSerializer, от которой производятся и DataContractSerializer, и NetDataContractSerializer.
В предыдущих трех случаях (сохранение типов .NET, сохранение графа объектов и полностью настраиваемая XmlObjectSerializer
сериализация) все требует подключения пользовательского сериализатора. Для этого выполните следующие действия.
Напишите собственное поведение, унаследованное от DataContractSerializerOperationBehavior.
Переопределите два метода
CreateSerializer
, чтобы вернуть собственный сериализатор (либо NetDataContractSerializer, либо DataContractSerializer сpreserveObjectReferences
, установленным в значениеtrue
, либо собственный пользовательский XmlObjectSerializer).Прежде чем открывать хост службы или создавать клиентский канал, удалите существующее DataContractSerializerOperationBehavior поведение и внедрите пользовательский производный класс, созданный на предыдущих шагах.
Дополнительные сведения о расширенных концепциях сериализации см. в разделе Сериализация и десериализация.