Создание контрактов служб

Этот раздел описывает, что такое контракты служб, как они определяются, какие операции доступны (также описаны последствия обмена сообщениями, на которых они основаны), какие типы данных используются; кроме того, он содержит ответы на другие вопросы, которые помогут при разработке операций, удовлетворяющих требованиям вашего сценария.

Создание контракта службы

Службы предоставляют несколько операций. В приложениях Windows Communication Foundation (WCF) определите операции, создав метод и помечая его атрибутом OperationContractAttribute . Затем, для создания контракта службы, необходимо сгруппировать операции либо объявив их в интерфейсе, отмеченном атрибутом ServiceContractAttribute, либо определив их в классе с таким же атрибутом. (Базовый пример см. в разделе Практическое руководство. Определение контракта службы.)

Любые методы, у которых нет атрибута OperationContractAttribute , не являются операциями службы и не предоставляются службами WCF.

Этот раздел рассказывает о принятии решений по следующим вопросам при создании контракта службы:

  • использовать ли классы или интерфейсы;

  • как задать типы данных, которыми нужно обмениваться;

  • какие типы шаблонов обмена можно использовать;

  • можно ли сделать явные требования безопасности частью контракта;

  • каковы ограничения для входных и выходных данных операций.

Классы или интерфейсы

Классы и интерфейсы представляют группирование функциональных возможностей и, следовательно, можно использовать для определения контракта службы WCF. Однако рекомендуется использовать интерфейсы, так как они напрямую моделируют контракты служб. Не имея реализации, интерфейсы всего лишь определяют группу методов с определенными сигнатурами. Реализуйте интерфейс контракта службы и реализовали службу WCF.

Все преимущества управляемых интерфейсов относятся также к интерфейсам контрактов служб:

  • интерфейсы контрактов служб могут расширять любое число других интерфейсов контрактов служб;

  • отдельный класс может реализовывать любое число контрактов служб, реализуя интерфейсы этих контрактов служб;

  • можно изменить реализацию контракта службы, изменив реализацию интерфейса и не изменяя сам контракт;

  • можно создать несколько версий службы, реализуя старый и новый интерфейсы. Старые клиенты могут подключаться к исходной версии, а более новые клиенты - к более новой версии.

Примечание.

При наследовании от других интерфейсов контрактов служб нельзя переопределять свойства операций, например имя или пространство имен. Если попытаться сделать это, будет создана новая операция в текущем контракте службы.

Пример использования интерфейса для создания контракта службы см. в статье "Практическое руководство. Создание службы с помощью интерфейса контракта".

Однако можно использовать класс для определения контракта службы и одновременной реализации этого контракта. Преимущество создания службы путем применения атрибутов ServiceContractAttribute и OperationContractAttribute напрямую к классу и методам класса, соответственно, - это скорость и простота. Недостаток состоит в том, что управляемые классы не поддерживают множественное наследование, и в результате могут реализовывать только один контракт службы за раз. Кроме того, любое изменение сигнатуры класса или метода изменяет открытый контракт для этой службы, что может предотвратить использование службы неизмененными клиентами. Дополнительные сведения см. в разделе "Реализация контрактов службы".

Пример, использующий класс для создания контракта службы и реализующего его одновременно, см. в статье "Практическое руководство. Создание службы с классом контракта".

К этому моменту у вас должно было сложиться представление о том, в чем заключается разница между определением контракта службы через интерфейс и через класс. Следующий шаг - определить, какие данные могут передаваться между службой и клиентами.

Параметры и возвращаемые значения

Каждая операция имеет возвращаемое значение и параметр, даже в случае void. Однако, в отличие от локального метода, в котором можно передавать ссылки на объекты от одного объекта к другому, операции служб не передают ссылки на объекты. Вместо этого они передают копии объектов.

Это важно, потому что каждый тип, используемый для параметра или возвращаемого значения, должен быть сериализуем. Это значит, должно быть возможно преобразование объекта этого типа в поток байтов и обратно из потока байтов в объект.

Примитивные типы сериализуемы по умолчанию, как и многие типы в .NET Framework.

Примечание.

Значения имен параметров в сигнатуре операции являются частью контракта и чувствительны к регистру. Если требуется использовать одно имя параметра локально, но изменить имя опубликованных метаданных, см. раздел System.ServiceModel.MessageParameterAttribute.

Контракты данных

Приложения, ориентированные на обслуживание, такие как приложения Windows Communication Foundation (WCF), предназначены для взаимодействия с самым широким числом клиентских приложений на платформах Майкрософт и не microsoft. Для наиболее широких возможностей взаимодействия рекомендуется помечать типы атрибутами DataContractAttribute и DataMemberAttribute для создания контракта данных - части контракта службы, описывающей данные, которыми могут обмениваться операции службы.

Контракты данных - включаемые по требованию контракты стилей: тип или элемент данных не сериализуются, если не применен явным образом атрибут контракта данных. Контракты данных не связаны с областью доступа управляемого кода: закрытые элементы данных могут быть сериализованы и отправлены куда-либо для открытого доступа. (Базовый пример контракта данных см. в разделе Практическое руководство. Создание базового контракта данных для класса или структуры.) WCF обрабатывает определение базовых сообщений SOAP, которые обеспечивают функциональность операции, а также сериализацию типов данных в тексте сообщений и из него. Если типы данных сериализуемы, нет необходимости думать об инфраструктуре обмена базовыми сообщениями при создании операций.

Хотя обычное приложение WCF использует DataContractAttribute и DataMemberAttribute атрибуты для создания контрактов данных для операций, можно использовать другие механизмы сериализации. Стандартные ISerializableи SerializableAttributeIXmlSerializable механизмы работают для обработки сериализации типов данных в базовые сообщения SOAP, которые переносят их из одного приложения в другое. Можно применить дополнительные стратегии сериализации, если типы данных требуют специальной поддержки. Дополнительные сведения о вариантах сериализации типов данных в приложениях WCF см. в разделе "Указание передачи данных в контрактах служб".

Сопоставление параметров и возвращаемых значений с обменом сообщениями

Операции службы поддерживаются при помощи базового обмена сообщениями SOAP, в которых передаются данные приложения в двух направлениях, а также данные, необходимые приложению для поддержки стандартной безопасности, транзакций и возможностей, связанных с сеансами. Так как это так, сигнатура операции службы определяет определенный базовый шаблон обмена сообщениями (MEP), который может поддерживать передачу данных и необходимые функции. В модели программирования WCF можно указать три шаблона: запрос и ответ, одностороннюю и дуплексную схему сообщений.

Запрос-ответ

Шаблон запрос-ответ означает, что отправитель запроса (клиентское приложение) получает ответ, который соответствует запросу. Это шаблон обмена сообщениями по умолчанию, так как он поддерживает операцию, в которую передается один или больше параметров и из которой происходит возврат результирующего значения вызывавшей стороне. Например, следующий пример кода на языке C# показывает основную операцию службы, которая получает и возвращает строку.

[OperationContractAttribute]  
string Hello(string greeting);  

Ниже приведен эквивалентный код Visual Basic.

<OperationContractAttribute()>  
Function Hello (ByVal greeting As String) As String  

Сигнатура этой операции диктует вид базового обмена сообщениями. Если корреляция не существует, WCF не может определить, для какой операции предназначено возвращаемое значение.

Обратите внимание, что если вы не укажете другой базовый шаблон сообщения, даже операции службы, возвращаемые void (Nothing в Visual Basic), обмениваются сообщениями запроса и ответа. Результат для операции такой: если клиент не вызывает операцию асинхронно, клиент приостанавливает обработку до получения возвращаемого сообщения, даже несмотря на то, что в нормальном случае это сообщение пустое. Следующий пример кода на C# показывает операцию, которая не отвечает, пока клиент не получит пустое сообщение в ответ.

[OperationContractAttribute]  
void Hello(string greeting);  

Ниже приведен эквивалентный код Visual Basic.

<OperationContractAttribute()>  
Sub Hello (ByVal greeting As String)  

Приведенный пример кода может понизить производительность и скорость ответа клиента, если выполнение операции требует длительного времени, но у операций типа запрос-ответ есть преимущества, даже если они возвращают void. Наиболее очевидное состоит в том, что сообщения о неисправностях SOAP могут возвращаться в сообщении ответа, что будет указывать на возникновение какой-либо связанной со службой ошибки либо при подключении, либо при обработке. Неисправности SOAP, заданные в контракте службы, передаются клиентскому приложению в виде объекта FaultException<TDetail>, где параметр типа - это тип, заданный в контракте службы. Это упрощает уведомление клиентов об ошибках в службах WCF. Дополнительные сведения об исключениях, сбоях SOAP и обработке ошибок см. в разделе "Указание и обработка ошибок" в контрактах и службах. Пример службы запроса и ответа и клиента см. в статье "Практическое руководство. Создание контракта запроса и ответа". Дополнительные сведения о проблемах с шаблоном ответа на запрос см. в разделе "Службы ответа на запросы".

Односторонний

Если клиент приложения службы WCF не должен ожидать завершения операции и не обрабатывает ошибки SOAP, операция может указать шаблон одностороннего сообщения. Односторонняя операция, в которой клиент вызывает операцию и продолжает обработку после записи сообщения в сеть WCF. Обычно это означает, что клиент продолжает работу почти без перерыва, если объем данных, передаваемых в исходящем сообщение не очень велик, и если не возникает ошибка при передаче данных. Этот шаблон обмена сообщениями поддерживает событийное взаимодействие между клиентом и приложением службы.

Обмен сообщениями, при котором одно сообщение отправляется и ни одного не принимается не поддерживает операции службы, для которых задано возвращаемое значение, отличное от void; в таком случае возникает исключение InvalidOperationException.

Отсутствие обратного сообщения также означает, что не может быть возвращено сообщение о неисправностях SOAP, указывающее на ошибки при обработке или подключении. (Передача сведений об ошибках подключения для односторонних операций требует дуплексного шаблона обмена сообщениями.)

Чтобы задать односторонний обмен сообщениями для операции, возвращающей void, присвойте свойству IsOneWay значение true, как в следующем примере кода C#.

[OperationContractAttribute(IsOneWay=true)]  
void Hello(string greeting);  

Ниже приведен эквивалентный код Visual Basic.

<OperationContractAttribute(IsOneWay := True)>  
Sub Hello (ByVal greeting As String)  

Метод тот же, что и в предшествующем примере типа запрос-ответ, но значение IsOneWay свойства true означает, что несмотря на то, что метод тот же, операция службы не посылает ответное сообщение и клиенты продолжают работу немедленно после передачи исходящего сообщения на уровень канала. Пример см. в разделе "Практическое руководство. Создание единого контракта". Дополнительные сведения о односторонней схеме см. в разделе "Односторонняя служба".

Duplex

Дуплексный шаблон характеризуется способностью и службы, и клиента отправлять сообщения друг другу, независимо от того, используется ли односторонний обмен сообщениями или запрос-ответ. Такая форма двустороннего общения полезна для служб, которым требуется напрямую обращаться к клиенту, или для асинхронного взаимодействия обеих сторон обмена сообщениями, в том числе для событийного поведения.

Дуплексный шаблон несколько более сложен, чем односторонний шаблон и шаблон запрос-ответ, так как имеет дополнительный механизм для взаимодействия с клиентом.

Для создания дуплексного контракта необходимо также создать контракт обратного вызова и присвоить тип этого контракта обратного вызова свойству CallbackContract атрибута ServiceContractAttribute контракта службы.

Для реализации дуплексного шаблона необходимо создать второй интерфейс, содержащий объявления методов, вызываемых на клиенте.

Пример создания службы и клиента, обращаюющегося к этой службе, см. в статье "Практическое руководство. Создание дуплексного контракта и практическое руководство. Службы Access с дуплексным контрактом". Рабочий пример см . в разделе Дуплекс. Дополнительные сведения о проблемах с использованием дуплексных контрактов см. в разделе Дуплексные службы.

Внимание

Когда служба получает дуплексное сообщение, она проверяет элемент ReplyTo входящего сообщения, чтобы определить, куда отправлять ответ. Если сообщение принимается по небезопасному каналу, ненадежный клиент может послать вредоносное сообщение с указанием компьютера для атаки в элементе ReplyTo, что приведет к отказу в обслуживании (DOS) этого компьютера.

Параметры Out и Ref

В большинстве случаев можно использовать in параметры (ByValв Visual Basic) и refout параметры (ByRefв Visual Basic). Так как параметры out и ref оба указывают на то, что операция возвращает данные, сигнатура операции, такая, как показана далее, задает необходимость операции запрос-ответ, несмотря на то что сигнатура операции возвращает void.

[ServiceContractAttribute]  
public interface IMyContract  
{  
  [OperationContractAttribute]  
  public void PopulateData(ref CustomDataType data);  
}  

Ниже приведен эквивалентный код Visual Basic.

<ServiceContractAttribute()> _  
Public Interface IMyContract  
  <OperationContractAttribute()> _  
  Public Sub PopulateData(ByRef data As CustomDataType)  
End Interface  

Исключение составляют только те случаи, когда сигнатура имеет определенную структуру. Например, можно использовать привязку NetMsmqBinding для взаимодействия с клиентами, только если метод, использованный для объявления операции, возвращает void; может не быть выходного значения, является ли оно возвращаемым значением, параметром ref или out.

Помимо этого, использование параметров out или ref требует, чтобы у операции было базовое ответное сообщение для передачи обратно измененного объекта. Если операция является односторонним, InvalidOperationException исключение создается во время выполнения.

Определение уровня защиты сообщений для контракта

При создании контракта необходимо также определить уровень защиты сообщений служб, реализующих этот контракт. Это необходимо, только если к привязке применена безопасность сообщений в конечной точке контракта. Если для привязки безопасность отключена (то есть, если привязка, предоставляемая системой, присваивает объекту System.ServiceModel.SecurityMode значение SecurityMode.None), то не нужно определять уровень безопасности сообщений для контракта. В большинстве случаев привязки, предоставляемые системой, с безопасностью на уровне сообщений предоставляют достаточный уровень защиты и нет необходимости рассматривать уровень защиты для каждой операции или для каждого сообщения.

Уровень защиты - это значение, которое определяет для сообщений (или частей сообщений), поддерживающих службу, подписываются ли они, подписываются и шифруются или отправляются без подписи и шифровки. Уровень защиты может быть задан в различных областях: на уровне службы, для конкретной операции, для сообщения операции или для части сообщения. Значения, заданные для более широкой области, становятся значениями по умолчанию для более узких областей, если не переопределить их явным образом. Если конфигурация привязки не может обеспечить минимальный необходимый уровень защиты для контракта, создается исключение. Если для контракта явно не задано значений уровня защиты, и если привязка имеет безопасность сообщений, уровнем защиты всех сообщений управляет конфигурация привязки. Это поведение принимается по умолчанию.

Внимание

Решение явно задать для различных областей контракта уровень защиты меньше максимального ProtectionLevel.EncryptAndSign обычно является компромиссом между степенью защиты и улучшением производительности. В таких случаях решения должны опираться на вид операций и ценность передаваемых данных. Дополнительные сведения см. в разделе "Защита служб".

Например, в следующем примере кода не задается ни свойствоProtectionLevel, ни свойство ProtectionLevel контракта.

[ServiceContract]  
public interface ISampleService  
{  
  [OperationContractAttribute]  
  public string GetString();  
  
  [OperationContractAttribute]  
  public int GetInt();
}  

Ниже приведен эквивалентный код Visual Basic.

<ServiceContractAttribute()> _  
Public Interface ISampleService  
  
  <OperationContractAttribute()> _  
  Public Function GetString()As String  
  
  <OperationContractAttribute()> _  
  Public Function GetData() As Integer  
  
End Interface  

При взаимодействии с реализацией ISampleService в конечной точке с привязкой WSHttpBinding по умолчанию (System.ServiceModel.SecurityMode по умолчанию, то есть значение Message), все сообщения зашифровываются и подписываются, так как это является уровнем защиты по умолчанию. Однако при использовании службы ISampleService с привязкой BasicHttpBinding по умолчанию (SecurityMode по умолчанию, то есть значение None), все сообщения отправляются в текстовом виде, так как эта привязка не имеет безопасности и, следовательно, уровень защиты игнорируется (то есть сообщения не шифруются и не подписываются). Если изменить значение свойства SecurityMode на Message, то сообщения будут зашифровываться и подписываться, так как теперь таков будет уровень защиты по умолчанию для привязки.

Если требуется явным образом задать или настроить требования по безопасности для контракта, задайте для свойства ProtectionLevel (или для любого из свойств ProtectionLevel более узкой области) уровень, требуемый для контракта службы. В данном случае явная настройка требует, чтобы привязка поддерживала эту настройку по крайней мере для указанной области. Например, в следующем примере кода явно задается одно значение ProtectionLevel для операции GetGuid.

[ServiceContract]  
public interface IExplicitProtectionLevelSampleService  
{  
  [OperationContractAttribute]  
  public string GetString();  
  
  [OperationContractAttribute(ProtectionLevel=ProtectionLevel.None)]  
  public int GetInt();
  [OperationContractAttribute(ProtectionLevel=ProtectionLevel.EncryptAndSign)]  
  public int GetGuid();
}  

Ниже приведен эквивалентный код Visual Basic.

<ServiceContract()> _
Public Interface IExplicitProtectionLevelSampleService
    <OperationContract()> _
    Public Function GetString() As String
    End Function
  
    <OperationContract(ProtectionLevel := ProtectionLevel.None)> _
    Public Function GetInt() As Integer
    End Function
  
    <OperationContractAttribute(ProtectionLevel := ProtectionLevel.EncryptAndSign)> _
    Public Function GetGuid() As Integer
    End Function
  
End Interface  

Служба, реализующая этот контракт IExplicitProtectionLevelSampleService и имеющая конечную точку, где используется привязка WSHttpBinding по умолчанию (System.ServiceModel.SecurityMode по умолчанию, то есть значение Message) ведет себя следующим образом:

  • Для операции GetString сообщения шифруются и подписываются.

  • Для операции GetInt сообщения не шифруются и не подписываются, то есть отправляются как простой текст.

  • GetGuid операции System.Guid возвращается в зашифрованном подписанном сообщении.

Дополнительные сведения о уровнях защиты и их использовании см. в разделе "Общие сведения о уровне защиты". Дополнительные сведения о безопасности см. в разделе "Защита служб".

Другие требования к сигнатуре операции

Некоторые возможности приложения требуют определенного вида сигнатуры операции. Например, привязка NetMsmqBinding поддерживает устойчивые службы и клиенты, позволяющие перезапуск приложения при установленном подключении, при этом приложение продолжит работу с того места, где остановилось, и ни одно сообщение не будет потеряно. (Дополнительные сведения см. в разделе Очереди в WCF.) Однако устойчивые операции должны принимать только один in параметр и не иметь возвращаемого значения.

Другой пример - использование типов Stream в операциях. Так как параметр Stream включает в себя тело сообщения целиком, если входные или выходные данные (то есть параметр ref, параметр out или возвращаемое значение) принадлежат типу Stream, это должны быть единственные входные и выходные данные, заданные для операции. Кроме того, параметр или тип возвращаемого значения должны являться объектами Stream, System.ServiceModel.Channels.Message или System.Xml.Serialization.IXmlSerializable. Дополнительные сведения о потоках см. в разделе "Большие данные" и "Потоковая передача".

Имена, пространства имен и запутывание

Имена и пространства имен типов .NET в определениях контрактов и операций важны при преобразовании контрактов в WSDL и при создании и отправке сообщений контрактов. Поэтому рекомендуется явно задавать имена и пространства имен контракта службы с помощью свойств Name и Namespace всех поддерживающих атрибутов контракта, например ServiceContractAttribute, OperationContractAttribute, DataContractAttribute, DataMemberAttribute и других атрибутов контракта.

Одним из следствий этого является то, что если имена и пространства имен не заданы явно, применение обфускации IL для сборки изменяет имена типов и пространства имен контракта, что приводит к измененному коду WSDL и обмену сообщениями, который обычно завершается ошибкой. Если вы не задаете явно имена и пространства имен контракта, но планируете использовать обфускацию, используйте атрибуты ObfuscationAttribute и ObfuscateAssemblyAttribute, чтобы предотвратить изменение имен и пространств имен контракта.

См. также