2017-02-10 36 views
0

簡短的故事是 - 我需要一個代碼庫的方式,以便能夠連接到多個SOAP API的每個除了XML Namespace逐個站點之外,API的WSDL本質上是相同的。不能從Magento的2 SOAP API反串行化SOAP響應 - XML命名空間之間的不匹配響應和服務參考WSDL

長的故事(抱歉,有很多的這個):

我的.NET 4.5的應用程序作爲客戶端的Magento的SOAP API(下載訂單,上傳產品,庫存水平等)。 應用程序使用服務引用來存儲Magento WSDL,對於Magento 1.x,這很好 - 應用程序可以在實例化客戶端時通過傳遞不同的端點URL來連接到任何網站的Magento API。

因此,然後Magento 2來了,我想創造一個新的版本的應用程序,可以與它接口。然而,一個重大的挑戰出現了。

我開始創建一個服務引用到一個已知的Magento 2網站API的WSDL(這不是直截了當,因爲在Magento 2下WSDL只在請求被OAUTH認證但是那是另外一個故事時暴露)。連接到同一個網站API時,應用程序工作正常。但是,當使用任何其他端點URL來實例化客戶端時,每個方法調用似乎都會導致一個空響應對象。如果服務引用從目標網站的WSDL重新創建,然後開始工作。很顯然,我無法做到這一點,併爲每個不同的目標網站編譯新版本的應用程序!

我看了看我的參考WSDL和彼此之間的差異,以及跟蹤的請求和響應與小提琴手,和我注意到,我認爲是問題的根源的東西。與Magento 1.x不同,Magento 2 WSDL具有特定於WSDL來自的網站的XML名稱空間。這轉化爲不同的命名空間中的值在類服務參考的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攔截響應,並與一個在我的Reference.cs更換指定的XML命名空間。我的代碼在下面,它似乎正確地進行替換,但仍然響應對象爲空!

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; 
} 

只有1在整個響應XML命名空間,就像我說的,我AfterReceiveReply方法似乎是使替代,所以我現在是爲下一步做什麼難住了!

迴應示例:

<?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> 

注:我有相匹配的類似的問題,在我的應用程序的服務請求會得到,除非請求XML命名空間(這是由Reference.cs給出)一個500錯誤響應目標網站。通過使用上述IClientMessageInspector的BeforeSendRequest方法進行替換,我成功解決了這個問題。爲了清楚起見,我已將該代碼留下。

回答

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); 
}