Как стать автором
Обновить

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

Время на прочтение5 мин
Количество просмотров5.9K
Яндекс.Директ работает некорректно по протоколу 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;
        }
    }
}

Теги:
Хабы:
Всего голосов 9: ↑7 и ↓2+5
Комментарии3

Публикации