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