2017-02-10 29 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); 
}