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

时间:2019-08-14 08:17:21

标签: c# xml wcf

我已经创建了WCF服务(.NET 4.6.2),并且第三方已经创建了客户端。

我无法控制客户端,也无法让第三方更改任何内容,因此任何更改都只能在服务器端进行。

我已将代码最小化和匿名化,以期希望以尽可能少的代码演示该问题。如果我犯了任何错误或您需要任何进一步的细节,我将相应地更改细节。


摘要

客户端能够调用服务,并且解析器能够正确地从SOAP Action中的代码中识别方法,但是传入的任何对象始终为null。我能够用断点捕获请求,并在运行时看到对象为空。

我已经确定问题主要是由客户端传递的XML名称空间前缀引起的。

我能够截获传入的原始消息,并且可以准确地看到客户端正在发送的内容。

我能够操纵这些传入消息进行实验,然后使用基于浏览器的通用客户端将消息的修改版本发布到服务以测试结果。


数据合同示例:-

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


服务合同示例:-

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


实现先前定义的服务合同的示例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;
    }
}


这是第三方向服务发布的内容:-

/*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>

中断代码,可以看到上面的结果导致调用了MyAction,但是MyObject为空


我修改了SOAP消息以删除空白的xmlns,但是someObject仍然为空

/*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>


我修改了SOAP消息以删除ns1前缀,但是someObject仍然为空

/*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>


现在,如果我同时删除了ns1前缀和空白的xmlns,那么someObject将按预期正确填充。这解决了所有问题。唯一的问题是,我无法让客户端进行此更改。

/*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>


首先,为什么会这样?据我了解,在XML中,前缀实际上与确定对象无关,因此<ns1:Something>应该与<ns2:Something><Something>是同一对象 但是,WCF解析器确定<ns1:Something>不是<Something>


我该如何解决此服务器端问题,最好是从WCF服务内部解决?我在想是否有一种方法可以在WCF服务中对其进行解析之前捕获该消息,并先去除ns1:和空白xmlns?

非常感谢

编辑

下面是一个示例,您可以复制/粘贴以准确地重现该问题:-

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

运行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>

现在,如果您添加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>

这是我无法解决的第一个问题

2 个答案:

答案 0 :(得分:0)

尝试以下操作:

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


}

答案 1 :(得分:0)

我已经花费了大量时间来寻找一种简单的解决方案,但是似乎不存在。

为了解决此问题,我最终研究了MessageInspectors,该工具允许在WCF服务处理原始SOAP消息之前对其进行查看和编辑。

创建一个实现IDispatchMessageInspector的类。这是进行实际过滤的地方

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

创建一个实现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;
    }
}

创建一个实现BehaviorExtensionElement的类

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

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

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

这是一个重量级的解决方案,但确实可以完美运行,学习此功能对于将来的项目肯定很有用。