无法对来自Magento 2 SOAP API的SOAP响应进行串行化-响应中的XML命名空间与服务引用WSDL不匹配



短篇小说是-我需要一种方法让单个代码库能够连接到多个SOAP API,其中每个API的WSDL基本上是相同的,只是XML命名空间因站点而异。

长话短说(很抱歉有很多这样的):

我的.NET 4.5应用程序充当Magento SOAP API的客户端(下载订单、上传产品、库存水平等)。该应用程序使用对库存Magento WSDL的服务引用,对于Magento 1.x来说,这很好-该应用程序可以连接到任何网站的Magento API,只需在实例化客户端时传递不同的端点URL。

于是,Magento 2出现了,我想制作一个可以与之接口的新版本的应用程序。然而,一个重大的挑战出现了。

我首先创建了一个已知的Magento 2网站API的WSDL的服务引用(这并不简单,因为在Magento 2中,只有在请求经过OAUTH身份验证时才会公开WSDL,但这是另一回事)。当连接到同一个网站API时,该应用程序运行良好。然而,当使用任何其他端点URL来实例化客户端时,每个方法调用似乎都会导致一个空响应对象。如果服务引用是从目标网站的WSDL重新创建的,那么它就开始工作了。显然,我无法做到这一点,并为每个不同的目标网站编译一个新版本的应用程序!

我查看了我的参考WSDL和另一个参考WSDL之间的差异,并用Fiddler跟踪了请求和响应,我注意到了一些我认为是问题根本原因的东西。与Magento 1.x不同,Magento 2 WSDL具有特定于WSDL来源网站的XML命名空间。这转换为服务引用的Reference.cs中类属性中的不同Namespace值,例如:

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响应不能取消序列化,除非响应中使用的XML命名空间与Reference.cs.中的类属性中的命名空间完全对应

最初,我尝试在运行时使用各种技术更改类属性值,但没有成功。

现在,我正试图使用IClientMessageInspector拦截响应,并将给定的XML命名空间替换为Reference.cs中的命名空间。我的代码如下,它似乎正确地进行了替换,但响应对象仍然为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;
}

整个响应中只有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>

注意:我遇到过类似的问题,我的应用程序的服务请求将得到500错误响应,除非请求中的XML命名空间(由Reference.cs提供)与目标站点匹配。我通过使用上述IClientMessageInspector的BeforeSendRequest方法进行替换,成功地解决了这一问题。为了清楚起见,我省略了那个代码。

我通过更改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);
}

最新更新