How can I create custom XML namespace attributes when consuming a legacy SOAP service?

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