Использование сервиса Yandex Direct на примере метода GetRegions (WCF клиент)

от автора

Яндекс.Директ работает некорректно по протоколу SOAP, так как фактическая схема данных, используемая в ответах на запросы не совпадает со схемой из предоставленного сервисом описания WSDL. А именно не совпадает пространство имен (вместо «API» приходит «namespaces.soaplite.com/perl» и «xml.apache.org/xml-soap»), а также имена параметров (например, в методе GetRegions вместо имени «return» приходит «ArrayOfRegionInfo»).

Для устранения первой проблемы был реализован перехват сообщений от сервиса и корректировка пространства имен.
Для устранения второй проблемы просто был откорректирован автосгенеренный по WSDL файл Reference.cs.
Официально поддержка, сервисом Яндекс.Директ, приложений, разрабатываемых на C#, с использованием WCF не осуществляется.
В Яндекс.Директ приведен пример по взаимодействию с сервисом посредством формата хранения данных JSON и простого WebClient. Данный пример работает корректно, в отличие от SOAP, но имеет ряд недостатков, таких как: нужно десериализировать полученный объект самостоятельно, нет поддержки контрактов между сервисом и клиентом.

Подготовка

Документация по сервису представлена здесь.
Для работы с сервисом необходимо:
1. зарегистрироваться (для передачи в параметре аутентификации login),
2. создать клиентское приложение (для передачи в параметре аутентификации application_id),
3. получить тестовый токен (для передачи в параметре аутентификации token),
4. можно использовать для передачи в параметре аутентификации locale значение «ru».
Более подробно можно прочитать в документации к сервису, не стоит ее здесь переписывать.

Создание приложения

1. Создаем новое приложение в MS Visual Studio.
2. Добавляем новую ссылку на сервис, указываем wsdl: «api.direct.yandex.ru/wsdl/v4», namespace, например, YandexAPIService (ну или как Вам угодно).
3. В файле Reference.cs тип System.Nullable<System.DateTime> заменяем на System.DateTime
4. В файле Reference.cs в описании метода YandexAPIService.APIPort.GetRegions имя параметра «return» заменить на «ArrayOfRegionInfo».
5. Создать MessageInspector, в котором будут перехватываться сообщения от сервиса и заменяться «неправильные» namespace на «правильный».

using System; using System.IO; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Configuration; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; using System.Xml;  namespace YandexDirectRegions {     // Client message inspector     public class YandexDirectMessageInspector : IClientMessageInspector     {         public void AfterReceiveReply(ref Message reply, object correlationState)         {             XmlDocument doc = new XmlDocument();             MemoryStream ms = new MemoryStream();              XmlWriter writer = XmlWriter.Create(ms);                          reply.WriteMessage(writer);             writer.Flush();              ms.Position = 0L;             doc.Load(ms);              foreach (XmlAttribute attr in doc.DocumentElement.Attributes)                 if (attr.Value == "http://namespaces.soaplite.com/perl" || attr.Value == "http://xml.apache.org/xml-soap")                     attr.Value = "API";              ms.Position = 0L;             ms.SetLength(0);             writer = XmlWriter.Create(ms);             doc.WriteTo(writer);             writer.Flush();                          ms.Position = 0L;             XmlReader reader = XmlReader.Create(ms);             Message newReply = Message.CreateMessage(reader, int.MaxValue, reply.Version);              newReply.Properties.CopyProperties(reply.Properties);             newReply.Headers.CopyHeadersFrom(reply);             reply = newReply;         }          public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)         {             return null;         }     }      // Endpoint behavior     public class YandexDirectEndpointBehavior : IEndpointBehavior     {         public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)         {             // No implementation necessary         }          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)         {             clientRuntime.MessageInspectors.Add(new YandexDirectMessageInspector());         }          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)         {             // No implementation necessary         }          public void Validate(ServiceEndpoint endpoint)         {             // No implementation necessary         }     }      // Configuration element      public class YandexDirectBehaviorExtension : BehaviorExtensionElement     {         public override Type BehaviorType         {             get { return typeof(YandexDirectEndpointBehavior); }         }          protected override object CreateBehavior()         {             // Create the  endpoint behavior that will insert the message             // inspector into the client runtime             return new YandexDirectEndpointBehavior();         }     } }  

6. В конфигурационном файле [app|web].config настраиваем endpoint

<?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.serviceModel>                <bindings>             <basicHttpBinding>                 <binding name="YandexDirectSoapBinding"                          maxReceivedMessageSize="100000">                     <security mode="Transport" />                 </binding>             </basicHttpBinding>         </bindings>                <client>             <endpoint address="https://soap.direct.yandex.ru/v4/soap/" binding="basicHttpBinding"                 bindingConfiguration="YandexDirectSoapBinding" contract="YandexAPIService.APIPort"                 behaviorConfiguration="YandexDirectBehavior"                 name="APIPort" />         </client>          <behaviors>           <endpointBehaviors>             <behavior name="YandexDirectBehavior">               <YandexDirectBehaviorExtension />             </behavior>           </endpointBehaviors>         </behaviors>          <extensions>           <behaviorExtensions>             <add               name="YandexDirectBehaviorExtension"               type="YandexDirectRegions.YandexDirectBehaviorExtension, YandexDirectRegions"           />           </behaviorExtensions>         </extensions>      </system.serviceModel> </configuration>  

7. Реализуем вызов метода

using System; using System.Linq; using System.ServiceModel; using System.ServiceModel.Channels; using YandexDirectRegions.YandexAPIService;  namespace YandexDirectRegions {     // класс приведен здесь для примера, но по хорошему нужно его вынести куда-нибудь в папку/проект "модели"     public class Region     {         public int RegionID { get; set; }         public int? ParentID { get; set; }         public string RegionName { get; set; }     }      public class UnitOfWork     {         YandexAPIService.APIPortClient yandexAPIPortClient;          #region Singleton          private static volatile UnitOfWork instance;         private static object syncRoot = new Object();          private UnitOfWork()         {             yandexAPIPortClient = new YandexAPIService.APIPortClient();             System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };         }          public static UnitOfWork Instance         {             get             {                 if (instance == null)                 {                     lock (syncRoot)                     {                         if (instance == null)                             instance = new UnitOfWork();                     }                 }                  return instance;             }         }          #endregion          public Region[] GetRegions(string application_id, string token, string login, string locale)         {             Region[] regions = null;              var applicationIdHeader = MessageHeader.CreateHeader("application_id", "ns", application_id);             var tokenHeader = MessageHeader.CreateHeader("token", "ns", token);             var loginHeader = MessageHeader.CreateHeader("login", "ns", login);             var localeHeader = MessageHeader.CreateHeader("locale", "ns", locale);              using (var scope = new OperationContextScope(yandexAPIPortClient.InnerChannel))             {                 OperationContext.Current.OutgoingMessageHeaders.Add(applicationIdHeader);                 OperationContext.Current.OutgoingMessageHeaders.Add(tokenHeader);                 OperationContext.Current.OutgoingMessageHeaders.Add(loginHeader);                 OperationContext.Current.OutgoingMessageHeaders.Add(localeHeader);                 var regionsInfo = yandexAPIPortClient.GetRegions();                 if (regionsInfo != null)                 {                     regions = regionsInfo.Select<RegionInfo, Region>((regionInfo) =>                                                                     {                                                                         return new Region()                                                                                     {                                                                                         RegionID = regionInfo.RegionID,                                                                                         ParentID = regionInfo.ParentID,                                                                                         RegionName = regionInfo.RegionName                                                                                     };                                                                     }                                                  ).ToArray();                 }              }              return regions;         }     } }  

ссылка на оригинал статьи http://habrahabr.ru/post/201746/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *