2017-02-10 30 views
0

Краткая история - мне нужен способ, чтобы одна база кода могла подключаться к нескольким API SOAP, где каждый WSDL API по сути тот же, за исключением того, что пространство имен XML изменяется по сайтам.Невозможно де-сериализовать ответы SOAP от Magento 2 SOAP API - несоответствие между пространством имен XML в ответе и ссылкой на службы WSDL

Длинная история (извините есть много этого):

Мои .NET 4.5 приложение выступает в качестве клиента к Magento SOAP API (загрузки заказов, загрузки продуктов, уровень запасов и т.д.). Приложение использует ссылку на службу для Magento WSDL, а для Magento 1.x это отлично работает - приложение может подключаться к API Magento любого веб-сайта, просто передавая другой URL конечной точки при создании экземпляра клиента.

Итак, Magento 2 пришел, и я хотел создать новую версию приложения, которое могло бы взаимодействовать с ним. Однако возникла серьезная проблема.

Я начал с создания сервисного справочника по известному WSDL API веб-сайта Magento 2 (это было непросто, поскольку в Magento 2 WSDL отображается только в том случае, если запрос аутентифицирован OAUTH, но это еще одна история). Приложение отлично работало при подключении к тому же API веб-сайта. Однако, когда любой другой конечный URL-адрес используется для создания экземпляра клиента, каждый вызов метода, похоже, приводит к объекту с нулевым ответом. Если ссылка на службу повторно создается из WSDL целевого веб-сайта, она начинает работать. Очевидно, я не могу этого сделать и скомпилировать новую версию приложения для каждого другого возможного целевого сайта!

Я рассмотрел разницу между моей ссылкой WSDL и другой и проследил запрос и ответ с помощью Fiddler, и я заметил то, что, по моему мнению, является основной причиной проблемы. В отличие от Magento 1.x, Magento 2 WSDL имеет пространство имен XML, специфичное для веб-сайта WSDL. Это приводит к различным значениям пространств имен в классе атрибутов в Reference.cs из справочной службы, например:

Magento 1.x атрибутов (обратите внимание на общую стоимость пространств имен):

[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:Magento")] 
[System.ServiceModel.ServiceContractAttribute(Namespace="urn:Magento", ConfigurationName="MagentoAPI.Mage_Api_Model_Server_Wsi_HandlerPortType")] 
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="urn:Magento", Order=0)] 

Magento 2 атрибуты:

[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1")] 
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1", ConfigurationName="MagentoV2SoapApiV1.SalesCreditmemoRepositoryV1.salesCreditmemoRepositoryV1PortType")] 
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1", Order=0)] 

Мой вывод заключается в том, что ответ SOAP не может быть deserialised, если XML-пространство имен используется в ответ точно не соответствует тому, что в классе атрибутов в Reference.cs.

Первоначально я попытался изменить значения атрибута класса во время выполнения using various techniques, но это не сработало.

Теперь я пытаюсь перехватить ответ с помощью IClientMessageInspector и заменить данное пространство имен XML тем, что указано в моем Reference.cs. Мой код ниже, и кажется, что он правильно заменил, но STILL - объект ответа null!

public class CustomInspectorBehavior : IEndpointBehavior 
{ 
    private readonly CustomMessageInspector _clientMessageInspector = new CustomMessageInspector(); 
    public string LastRequestXml { get { return _clientMessageInspector.LastRequestXml; } } 
    public string LastResponseXml { get { return _clientMessageInspector.LastRequestXml; } } 
    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {} 
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {} 
    public void Validate(ServiceEndpoint endpoint) {} 
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(_clientMessageInspector); } 
} 

public class CustomMessageInspector : IClientMessageInspector 
{ 
    public string LastRequestXml { get; private set; } 
    public string LastResponseXml { get; private set; } 
    public void AfterReceiveReply(ref Message reply, object correlationState) 
    { 
     LastResponseXml = reply.ToString(); 

     var doc = new XmlDocument(); 
     var ms = new MemoryStream(); 
     var writer = XmlWriter.Create(ms); 
     reply.WriteMessage(writer); 
     writer.Flush(); 
     ms.Position = 0; 

     // Do namespace substitution 
     doc.Load(ms); 
     doc.DocumentElement.SetAttribute("xmlns:ns1", "http://www.my-reference-address.net/soap/default?services=salesCreditmemoRepositoryV1"); 

     ms.SetLength(0); 
     writer = XmlWriter.Create(ms); 
     doc.WriteTo(writer); 
     writer.Flush(); 
     ms.Position = 0; 

     var reader = XmlReader.Create(ms); 
     reply = Message.CreateMessage(reader, int.MaxValue, reply.Version); 
    } 
    public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel) { LastRequestXml = request.ToString(); } 
} 


public static salesCreditmemoRepositoryV1PortTypeClient GetCreditMemosServiceClient(string apiAddress) 
{ 
    const string serviceName = "salesCreditmemoRepositoryV1"; 
    var apiClient = new salesCreditmemoRepositoryV1PortTypeClient(GetSoap12Binding(), new EndpointAddress(apiAddress)); 
    var requestInterceptor = new CustomInspectorBehavior(); 
    apiClient.Endpoint.Behaviors.Add(requestInterceptor); 
    return apiClient; 
} 

Существует только один XML-пространства в целом ответ, и, как я уже говорил, мой метод AfterReceiveReply, кажется, делает замену, так что теперь я действительно STUCK для того, что делать дальше!

Пример ответа:

<?xml version="1.0" encoding="UTF-8"?> 
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1"> 
    <env:Body> 
     <ns1:salesCreditmemoRepositoryV1GetListResponse> 
      <result> 
       <items/> 
       <searchCriteria> 
        <filterGroups> 
         <item> 
          <filters> 
          </filters> 
         </item> 
        </filterGroups> 
       </searchCriteria> 
       <totalCount>0</totalCount> 
      </result> 
     </ns1:salesCreditmemoRepositoryV1GetListResponse> 
    </env:Body> 
</env:Envelope> 

Примечание: У меня была аналогичная проблема, когда запрос на обслуживание моего приложения будет получить ответ 500 ошибку, если в XML-пространство имен в запросе (который задается Reference.cs) согласованного целевой сайт. Я успешно справился с этим, выполнив подстановку с использованием метода BeforeSendRequest вышеописанного IClientMessageInspector. Я оставил этот код для ясности.

ответ

0

Я получил его, изменив метод AfterReceiveReply. По какой-то причине использование XmlDocument для создания модифицированного ответа работает.

private const string ReplyXmlNameSpacePattern = @"xmlns:ns1=""(.+)\?services=(.+)"""; 

public void AfterReceiveReply(ref Message reply, object correlationState) 
{ 
    // Read reply XML 
    var doc = new XmlDocument(); 
    var ms = new MemoryStream(); 
    var writer = XmlWriter.Create(ms); 
    reply.WriteMessage(writer); 
    writer.Flush(); 
    ms.Position = 0; 
    doc.Load(ms); 

    // Replace XML namespace in SOAP envelope 
    var replacementXmlNameSpace = @"xmlns:ns1=""http://www.my-reference-address.net/soap/default?services=$2"""; 
    var newReplyXml = Regex.Replace(doc.OuterXml, ReplyXmlNameSpacePattern, replacementXmlNameSpace, RegexOptions.Compiled); 
    doc.LoadXml(newReplyXml); 

    // Write out the modified reply 
    ms.SetLength(0); 
    writer = XmlWriter.Create(ms); 
    doc.WriteTo(writer); 
    writer.Flush(); 
    ms.Position = 0; 
    var reader = XmlReader.Create(ms); 
    reply = Message.CreateMessage(reader, int.MaxValue, reply.Version); 
}