Wcf 无法从Magento 2 SOAP API反序列化SOAP响应-响应中的XML命名空间与服务引用WSDL不匹配

Wcf 无法从Magento 2 SOAP API反序列化SOAP响应-响应中的XML命名空间与服务引用WSDL不匹配,wcf,soap,wsdl,xml-namespaces,magento2,Wcf,Soap,Wsdl,Xml Namespaces,Magento2,简而言之,我需要一种方法,使单个代码库能够连接到多个SOAP API,其中每个API的WSDL本质上是相同的,只是XML命名空间因站点而异 说来话长(很抱歉,有很多这样的事情): 我的.NET4.5应用程序充当MagentoSOAPAPI的客户端(下载订单、上传产品、库存水平等)。 该应用程序使用对股票Magento WSDL的服务引用,对于Magento 1.x来说,这很好——应用程序可以连接到任何网站的Magento API,只要在实例化客户端时传递不同的端点URL即可 于是Magento

简而言之,我需要一种方法,使单个代码库能够连接到多个SOAP API,其中每个API的WSDL本质上是相同的,只是XML命名空间因站点而异

说来话长(很抱歉,有很多这样的事情):

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

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

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

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

Magento 1.x属性(请注意通用名称空间值):

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)] 
我的结论是,除非响应中使用的XML名称空间与Reference.cs中的类属性中的名称空间完全对应,否则SOAP响应无法反序列化

最初,我尝试在运行时更改类属性值,但这不起作用

现在,我尝试使用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;
}
整个响应中只有一个XML名称空间,正如我所说的,我的AfterReceivePly方法似乎正在进行替换,所以我现在真的被困在下一步该做什么

答复示例:

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

0

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

我通过更改AfterReceivePly方法使其正常工作。出于某种原因,使用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);
}
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);
}