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

в 13:52, , рубрики: .net, wcf, Yandex API, метки: , ,

Яндекс.Директ работает некорректно по протоколу 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;
        }
    }
}

Автор: rinader

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js