简体   繁体   English

WCF客户端,XML名称空间前缀导致空对象

[英]WCF client, XML namespace prefix results in null object

I have created a WCF service (.NET 4.6.2) and a third party has created the client. 我已经创建了WCF服务(.NET 4.6.2),并且第三方已经创建了客户端。

I have no control over the client, or the ability to get the third party to change anything, so any changes are going to be on the server side only. 我无法控制客户端,也无法控制第三方进行任何更改,因此任何更改都只能在服务器端进行。

I have minimised and anonymised the code to the bare minimum to hopefully demonstrate the problem in as little code as possible. 我已将代码最小化和匿名化到最低限度,以期希望以尽可能少的代码演示该问题。 If I made any mistakes or you need any further detail, I will alter the details accordingly. 如果我犯了任何错误或您需要任何进一步的细节,我将相应地更改细节。


Summary 摘要

The client is able to call the service, and the parser is able to correctly identify the method in code from the SOAP Action, however any objects passed in are always null. 客户端能够调用服务,并且解析器能够正确地从SOAP Action中的代码中识别方法,但是传入的任何对象始终为null。 I am able to catch the requests with a breakpoint and see that the objects are null at runtime. 我能够用断点捕获请求,并在运行时看到对象为空。

I have identified that the problem is being caused mostly my an XML namespace prefix that the client is passing. 我已经确定问题主要是由客户端传递的XML名称空间前缀引起的。

I am able to intercept the incoming raw messages and I can see exactly what the client is sending. 我能够截获传入的原始消息,并且可以确切地看到客户端正在发送什么。

I am able to manipulate these incoming messages, for experimentation, and then post the modified version of the message to the service to test the results using a generic browser based client. 我能够操纵这些传入消息进行实验,然后使用基于浏览器的通用客户端将消息的修改版本发布到服务以测试结果。


Example Data Contract:- 数据合同示例:-

[DataContract(Namespace = "http://mycompanyname.co.uk")]
public class SomeObject
{
    [DataMember]
    public string SomeField { get; set; }
}


Example Service Contract:- 服务合同示例:-

[ServiceContract(Namespace = "http://mycompanyname.co.uk")]
public interface IIncoming
{
    [OperationContract]
    XmlElement MyAction(SomeObject someObject);
}


Example WCF service implementing the previously defined Service Contract:- 实现先前定义的服务合同的示例WCF服务:

[ServiceBehavior(Namespace = "http://mycompanyname.co.uk")]
public class Incoming : IIncoming
{
    public XmlElement MyAction(SomeObject someObject)
    {
        XmlDocument response = new XmlDocument();
        response.LoadXml("<Response>OK</Response>");
        return response.DocumentElement;
    }
}


This is what the 3rd party posts to the service:- 这是第三者发布到服务的内容:

/*3rd party original*/
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns1:MyAction xmlns:ns1="http://mycompanyname.co.uk">
            <someObject xmlns="">
                <SomeField>Blah</SomeField>
            </someObject>
        </ns1:MyAction>
    </soapenv:Body>
</soapenv:Envelope>

Breakpointing the code, I can see that the above results in MyAction being called, but MyObject is null 断点代码,我可以看到上面的结果导致调用了MyAction,但是MyObject为null


I modified the SOAP message to remove the blank xmlns, but someObject is still null 我修改了SOAP消息以删除空白的xmlns,但是someObject仍然为null

/*blank xmlns removed*/
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns1:MyAction xmlns:ns1="http://mycompanyname.co.uk">
            <someObject>
                <SomeField>Blah</SomeField>
            </someObject>
        </ns1:MyAction>
    </soapenv:Body>
</soapenv:Envelope>


I modified the SOAP message to remove the ns1 prefix, but someObject is still null 我修改了SOAP消息以删除ns1前缀,但是someObject仍然为null

/*ns1 removed*/
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <MyAction xmlns="http://mycompanyname.co.uk">
            <someObject xmlns="">
                <SomeField>Blah</SomeField>
            </someObject>
        </MyAction>
    </soapenv:Body>
</soapenv:Envelope>


Now if I both remove the ns1 prefix AND the blank xmlns, then someObject is correctly populated as expected. 现在,如果我都删除了ns1前缀和空白xmlns,那么someObject将按预期正确地填充。 This resolves everything. 这解决了所有问题。 The only problem is, I can't get the client to make this change. 唯一的问题是,我无法让客户进行此更改。

/*both removed - works*/
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <MyAction xmlns="http://mycompanyname.co.uk">
            <someObject>
                <SomeField>Blah</SomeField>
            </someObject>
        </MyAction>
    </soapenv:Body>
</soapenv:Envelope>


So firstly, why does this happen? 首先,为什么会这样? as I understand it in XML, the prefix is effectively irrelevant in determining an object, so <ns1:Something> should be the same object as <ns2:Something> or <Something> The WCF parser is however determining that <ns1:Something> is not a <Something> 据我了解,在XML中,前缀实际上与确定对象无关,因此<ns1:Something>应该与<ns2:Something><Something>是同一对象。但是WCF解析器确定<ns1:Something>不是<Something>


How can I resolve this problem server side, preferably from within the WCF service? 如何解决此问题,最好从WCF服务中解决? I was thinking if there is a way to capture the message before it is parsed in the WCF service and strip the ns1: and blank xmlns out beforehand? 我在想是否有一种方法可以在WCF服务中对其进行解析之前捕获该消息,并先去除ns1:和空白xmlns?

Many thanks 非常感谢

EDIT 编辑

Here is an example that you can copy/paste to reproduce the problem exactly:- 这是一个示例,您可以复制/粘贴以准确重现该问题:-

Create a WCF Service Application (.NET Framework 4.6.2) 创建WCF服务应用程序(.NET Framework 4.6.2)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Xml;

namespace GenericIncoming
{
    [ServiceContract(Namespace = "http://mycompanyname.co.uk")]
    public interface IIncoming
    {
        [OperationContract]
        XmlElement MyAction(SomeObject someObject);
    }

    [DataContract(Namespace ="")]
    public class SomeObject
    {
        [DataMember]
        public string SomeField { get; set; }
    }
}



using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Xml;

namespace GenericIncoming
{
    [ServiceBehavior(Namespace = "http://mycompanyname.co.uk")]
    public class Incoming : IIncoming
    {
        public XmlElement MyAction(SomeObject someObject)
        {
            XmlDocument response = new XmlDocument();
            if (someObject != null)
            {
                response.LoadXml("<Response>OK</Response>");
            }
            else
            {
                response.LoadXml("<Response>NULL</Response>");
            }
            return response.DocumentElement;
        }
    }
}

Run your WCF service and POST this message to the service, and you will get an "OK" response 运行您的WCF服务并将此消息发布到该服务,您将收到“确定”响应

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <MyAction xmlns="http://mycompanyname.co.uk">
            <someObject>
                <SomeField>Blah</SomeField>
            </someObject>
        </MyAction>
    </s:Body>
</s:Envelope>

Now if you add the ns1 prefix, just like the 3rd party sends to me, then the response is "NULL" this means that the object in the WCF is null because the parser was unable to deal with the XML 现在,如果您添加ns1前缀(就像第三方发送给我的一样),则响应为“ NULL”,这意味着WCF中的对象为null,因为解析器无法处理XML

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <ns1:MyAction xmlns:ns1="http://mycompanyname.co.uk">
            <someObject>
                <SomeField>Blah</SomeField>
            </someObject>
        </ns1:MyAction>
    </s:Body>
</s:Envelope>

This is the first problem that I am unable to resolve 这是我无法解决的第一个问题

Try following : 尝试以下操作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication1
{
    public class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            string response = File.ReadAllText(FILENAME);
            StringReader sReader = new StringReader(response);

            XmlSerializer serializer = new XmlSerializer(typeof(Envelope));
            Envelope envelope = (Envelope)serializer.Deserialize(sReader);

        }
    }
    [XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
    public class Envelope
    {
        [XmlElement(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
        public Body body { get;set;}
    }
    [XmlRoot(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
    public class Body
    {
        [XmlElement(ElementName = "MyAction", Namespace = "http://mycompanyname.co.uk")]
        public MyAction myAction { get; set; }
    }
    [XmlRoot(ElementName = "MyAction", Namespace = "http://mycompanyname.co.uk")]
    public class MyAction
    {
        [XmlElement(ElementName = "someObject", Namespace = "")]
        public SomeObject someObject { get; set; }
    }
    [XmlRoot(ElementName = "someObject", Namespace = "")]
    public class SomeObject
    {
        public string SomeField { get; set; }
    }


}

I've invested a huge amount of time looking for a simple solution, but one doesn't seem to exist. 我花了大量时间寻找一种简单的解决方案,但似乎不存在。

In order to resolve this problem, I ended up researching MessageInspectors, which allow the viewing and editing of the raw SOAP messages before they are processed by the WCF service. 为了解决此问题,我最终研究了MessageInspector,该工具允许在WCF服务处理原始SOAP消息之前查看和编辑它们。

Create a class that implements IDispatchMessageInspector. 创建一个实现IDispatchMessageInspector的类。 This is where the actual filtering will occur 这是实际过滤的地方

public class CorrectorInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        request = FilterMessage(request);
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        return;
    }

    private Message FilterMessage(Message originalMessage)
    {
        MemoryStream memoryStream = new MemoryStream();
        XmlWriter xmlWriter = XmlWriter.Create(memoryStream);
        originalMessage.WriteMessage(xmlWriter);
        xmlWriter.Flush();
        string body = Encoding.UTF8.GetString(memoryStream.ToArray());
        xmlWriter.Close();

        //Remove the ns1 prefix
        body = body.Replace("ns1:", "");
        body = body.Replace(":ns1", "");
        //remove the blank namespace 
        body = body.Replace(" xmlns=\"\"","");

        memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(body));
        XmlDictionaryReader xmlDictionaryReader = XmlDictionaryReader.CreateTextReader(memoryStream, new XmlDictionaryReaderQuotas());
        Message newMessage = Message.CreateMessage(xmlDictionaryReader, int.MaxValue, originalMessage.Version);
        newMessage.Properties.CopyProperties(originalMessage.Properties);
        return newMessage;
    }
}

Create a class that implements IEndpointBehavior 创建一个实现IEndpointBehavior的类

public class CorrectorBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
        return;
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        return;
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        CorrectorInspector inspector = new CorrectorInspector();
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
        return;
    }
}

Create a class that implements BehaviorExtensionElement 创建一个实现BehaviorExtensionElement的类

public class CorrectorBehaviourExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(CorrectorBehavior);
        }
    }

    protected override object CreateBehavior()
    {
        return new CorrectorBehavior();
    }
}

In the web.config, we need to define the behaviorExtension, the serviceBehavior, and then we need to specify our newly created behaviour against the service binding 在web.config中,我们需要定义behaviorExtension,serviceBehavior,然后需要针对服务绑定指定新创建的行为

 <system.serviceModel>
    <services>
      <service name="GenericIncoming.Incoming">
        <endpoint address="" binding="basicHttpBinding" contract="GenericIncoming.IIncoming" behaviorConfiguration="correctorBehavior" />
      </service>
    </services>    
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="correctorBehavior">
          <serviceFilter />
        </behavior>
      </endpointBehaviors>      
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="serviceFilter" type="GenericIncoming.CorrectorBehaviourExtensionElement, GenericIncoming, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </behaviorExtensions>
    </extensions>
    <protocolMapping>
        <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>    
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

This is a fairly heavyweight solution, but it does indeed work perfectly, and learning this functionality will surely be useful for future projects. 这是一个相当重量级的解决方案,但是它确实可以完美地工作,并且学习此功能对于将来的项目肯定很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM