使用旧 SOAP 服务时如何创建自定义 XML 命名空间属性? [英] How can I create custom XML namespace attributes when consuming a legacy SOAP service?

查看:31
本文介绍了使用旧 SOAP 服务时如何创建自定义 XML 命名空间属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个旧的 Tibco SOAP 服务,我需要从中获取一些数据.不幸的是,该服务非常注重请求消息上的 XML 命名空间属性.我在使用 PeopleSoft 的服务时也遇到过这类问题(https://en.wikipedia.org/wiki/PeopleCode).

我从服务中获取了 .wsdl 并创建了一个服务引用.

开箱即用,.Net 产生的 XML 请求消息是:

<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><getLocation xmlns="http://customNamespaceHere"><上下文><source>SysAdmin</source></上下文><地址><address1>123 Main St</address1><city>纽约</city><state>纽约</state><国家>美国</国家></地址></getLocation></s:身体></s:信封>

真正有效的是(我使用 SoapUI 发现了这一点):

<s:身体><getLocation xmlns="http://customNamespaceHere"><上下文><source>SysAdmin</source></上下文><地址><address1>123 Main St</address1><city>纽约</city><state>纽约</state><国家>美国</国家></地址></getLocation></s:身体></s:信封>

注意 body 标签中没有 xsi 和 xsd 前缀.

问:我怎样才能让 .Net 发送正确的 XML,而无需手动滚动 XML 文档并手动将其发送到服务?

解决方案

答:通过实现 IClientMessageInspector,我能够在发送之前修改 XML 请求.

首先,我必须通过实现 IClientMessageInspector 来扩展 WCF.这使我能够访问请求对象并修改 XML 请求消息.这些类不需要位于您的解决方案中的任何特定位置(据我所知).

公共类 ExtendedClientMessageInspector : IClientMessageInspector{//在这里,我们可以在发送之前更改 xml 请求正文.公共对象 BeforeSendRequest(ref Message request, IClientChannel channel){return TibcoService.ModifyGetLocationRequest(ref request);}//结尾public void AfterReceiveReply(ref 消息回复,对象相关状态){返回;}//结尾}//结束类公共类 ExtendedEndpointBehavior : IEndpointBehavior{公共无效AddBindingParameters(ServiceEndpoint端点,BindingParameterCollection bindingParameters){返回;}//结尾public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime){clientRuntime.MessageInspectors.Add(new ExtendedClientMessageInspector());}//结尾public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher){返回;}//结尾公共无效验证(ServiceEndpoint 端点){返回;}//结尾}//结束类公共类 EndpointBehaviorExtensionElement : BehaviorExtensionElement{受保护的覆盖对象 CreateBehavior(){返回新的扩展端点行为();}//结尾公共覆盖类型行为类型{得到{返回 typeof(ExtendedEndpointBehavior);}}//结尾}//结束类

我确实将这个服务中专门处理XML的方法与服务放在同一个类中:

public static Message ModifyGetLocationRequest(ref Message request){//初始化对象var xmlDocument = new XmlDocument();var memoryStream = new MemoryStream();var xmlWriter = XmlWriter.Create(memoryStream);var xmlAttribute = xmlDocument.CreateAttribute("xmlns", "api", "http://www.w3.org/2000/xmlns/");xmlAttribute.Value = "http://customNamespaceHere";//将xml请求消息写入内存流request.WriteMessage(xmlWriter);//清除 xmlWriterxmlWriter.Flush();//把memoryStream中的指针放到开头memoryStream.Position = 0;//将内存流加载到 xmlDocument 中xmlDocument.Load(memoryStream);//从顶部向下删除第二个节点的属性xmlDocument.ChildNodes[1].ChildNodes[1].Attributes.RemoveAll();//重置 memoryStream 对象 - 本质上是将它清空memoryStream.SetLength(0);//重新初始化 xmlWriterxmlWriter = XmlWriter.Create(memoryStream);//将修改后的xml请求消息(xmlDocument)写入xmlWriter中的memoryStreamxmlDocument.WriteTo(xmlWriter);//清除 xmlWriterxmlWriter.Flush();//把memoryStream中的指针放到开头memoryStream.Position = 0;//使用包含 xmlDocument 的 memoryStream 创建一个新的 xmlReadervar xmlReader = XmlReader.Create(memoryStream);//使用修改后的 xmlDocument 创建一个新的请求消息request = Message.CreateMessage(xmlReader, int.MaxValue, request.Version);退货请求;}//结尾

要在调用服务时使这一切正常工作,您需要修改您的 web.config 或 app.config.在您的端点声明中,您需要指定一个 behaviorConfiguration 元素,如下所示:

<端点地址="http://1.2.3.4:1234/InventoryBinding"binding="basicHttpBinding" bindingConfiguration="HttpSoapBinding"合同="TibcoSvc.InventoryPort" name="InventoryPort"行为配置=clientInspectorsAdded"/></客户端>

您还需要指定扩展名和行为:

<扩展><行为扩展><add name="ExtendedEndpointBehavior" type="Sample.Integrations.EndpointBehaviorExtensionElement, Sample.Integrations, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"/></behaviorExtensions></扩展名><行为><端点行为><行为名称="clientInspectorsAdded"><扩展端点行为/></行为></endpointBehaviors></行为></system.serviceModel>

请注意,扩展名必须与返回的名称完全一致:

var foo = typeof(.EndpointBehaviorExtensionElement).AssemblyQualifiedName;

这里有几个我觉得有用的链接:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/01440583-d406-4426-8667-63c6eda431fa/remove-xmlnsxsi-and-xmlnsxsd-from-soap-request-body-tag-aspnet?forum=wcf

https://social.msdn.microsoft.com/Forums/vstudio/en-US/51547537-fdae-4837-9bd1-30e445d378e9/removing-xmlnsxsihttpwwww3org2001xmlschemainstance-and?forum>

http://weblogs.asp.net/paolopia/writing-a-wcf-message-inspector

https:///msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector(v=vs.100).aspx

I have a legacy Tibco SOAP service that I need to get some data from. Unfortunately this service is very particular about the XML namespace attributes on the request message. I have also run into this sort of problem when consuming services from PeopleSoft (https://en.wikipedia.org/wiki/PeopleCode).

I got the .wsdl from the service and created a service reference.

Out of the box, the XML request message that .Net produces is:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <getLocation xmlns="http://customNamespaceHere">
            <context>
                <source>SysAdmin</source>
            </context>
            <Address>
                <address1>123 Main St</address1>
                <city>New York</city>
                <state>NY</state>
                <country>US</country>
            </Address>
        </getLocation>
    </s:Body>
</s:Envelope>

What actually works is (I figured this out using SoapUI):

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <getLocation xmlns="http://customNamespaceHere">
            <context>
                <source>SysAdmin</source>
            </context>
            <Address>
                <address1>123 Main St</address1>
                <city>New York</city>
                <state>NY</state>
                <country>US</country>
            </Address>
        </getLocation>
    </s:Body>
</s:Envelope>

Notice the absence of the xsi and xsd prefixes within the body tag.

Q: How can I get .Net to send the correct XML short of hand rolling the XML document and manually sending it to the service?

解决方案

A: I was able to modify the XML request before sending it out by implementing IClientMessageInspector.

First I had to extend WCF by implementing IClientMessageInspector. This allowed me to gain access to the request object and modify the XML request message. These classes don't need to be in any particular location within you solution (as far as I know).

public class ExtendedClientMessageInspector : IClientMessageInspector
{
    // Here we can alter the xml request body before it gets sent out.
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        return TibcoService.ModifyGetLocationRequest(ref request);

    } // end

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

    } //end

} // end class

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

    } // end

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new ExtendedClientMessageInspector());

    } // end

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        return;

    } // end

    public void Validate(ServiceEndpoint endpoint)
    {
        return;

    } // end

} // end class

public class EndpointBehaviorExtensionElement : BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new ExtendedEndpointBehavior();

    } // end

    public override Type BehaviorType
    {
        get
        {
            return typeof(ExtendedEndpointBehavior);
        }

    } // end

} // end class

I did put the method that specifically deals with the XML in this service in the same class as the service:

public static Message ModifyGetLocationRequest(ref Message request)
{
    // Initialize objects
    var xmlDocument = new XmlDocument();
    var memoryStream = new MemoryStream();
    var xmlWriter = XmlWriter.Create(memoryStream);
    var xmlAttribute = xmlDocument.CreateAttribute("xmlns", "api", "http://www.w3.org/2000/xmlns/");

    xmlAttribute.Value = "http://customNamespaceHere";

    // Write the xml request message into the memory stream
    request.WriteMessage(xmlWriter);

    // Clear the xmlWriter
    xmlWriter.Flush();

    // Place the pointer in the memoryStream to the beginning 
    memoryStream.Position = 0;

    // Load the memory stream into the xmlDocument
    xmlDocument.Load(memoryStream);

    // Remove the attributes from the second node down form the top
    xmlDocument.ChildNodes[1].ChildNodes[1].Attributes.RemoveAll();

    // Reset the memoryStream object - essentially nulls it out
    memoryStream.SetLength(0);

    // ReInitialize the xmlWriter
    xmlWriter = XmlWriter.Create(memoryStream);

    // Write the modified xml request message (xmlDocument) to the memoryStream in the xmlWriter
    xmlDocument.WriteTo(xmlWriter);

    // Clear the xmlWriter
    xmlWriter.Flush();

    // Place the pointer in the memoryStream to the beginning 
    memoryStream.Position = 0;

    // Create a new xmlReader with the memoryStream that contains the xmlDocument
    var xmlReader = XmlReader.Create(memoryStream);

    // Create a new request message with the modified xmlDocument
    request = Message.CreateMessage(xmlReader, int.MaxValue, request.Version);

    return request;

} // end

To get this all to work when the service is called you will need to modify your web.config or app.config. On your endpoint declaration you will need to specify a behaviorConfiguration element like so:

<client>
    <endpoint address="http://1.2.3.4:1234/InventoryBinding"
          binding="basicHttpBinding" bindingConfiguration="HttpSoapBinding"
          contract="TibcoSvc.InventoryPort" name="InventoryPort" 
          behaviorConfiguration="clientInspectorsAdded" />    
</client>

You will also need to specify the extension and the behavior as well:

<system.serviceModel>

    <extensions>
        <behaviorExtensions>
            <add name="ExtendedEndpointBehavior" type="Sample.Integrations.EndpointBehaviorExtensionElement, Sample.Integrations, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </behaviorExtensions>
    </extensions>

    <behaviors>
        <endpointBehaviors>
            <behavior name="clientInspectorsAdded">
                <ExtendedEndpointBehavior />
            </behavior>
        </endpointBehaviors>
    </behaviors>

</system.serviceModel>

Note here that the extension name needs to be exactly what is returned by:

var foo = typeof(<PutYourNamespaceHereNoAngleBrackets>.EndpointBehaviorExtensionElement).AssemblyQualifiedName; 

Here are a few links that I found helpful:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/01440583-d406-4426-8667-63c6eda431fa/remove-xmlnsxsi-and-xmlnsxsd-from-soap-request-body-tag-aspnet?forum=wcf

https://social.msdn.microsoft.com/Forums/vstudio/en-US/51547537-fdae-4837-9bd1-30e445d378e9/removing-xmlnsxsihttpwwww3org2001xmlschemainstance-and?forum=wcf

http://weblogs.asp.net/paolopia/writing-a-wcf-message-inspector

https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector(v=vs.100).aspx

这篇关于使用旧 SOAP 服务时如何创建自定义 XML 命名空间属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆