Search code examples
.netwcf

WCF Addressing - Remove the WSA:TO element


I have a working set of working WCF web services coded up in .NET 4.6 - they are making outbound calls to a server. So, they are running in a .EXE (actually will eventually run as a Windows Service).

These web services need to support the WS-Addressing standard:

W3C Web Services Addressing 1.0 - Core http://www.w3.org/TR/2006/REC-ws-addr-core-20060509

That version of the standard states that the WSA:TO element is optional. What I need is for the WSA:TO element to not to appear in the SOAP output at all. AND I want to do this without having to write a custom SOAP writer as I also need to use WS-SECURITY. I've googled etc etc.

In my binding configuration I have:

    <binding name="MyServiceThatMustNotSendWSATO">
      <textMessageEncoding messageVersion="Soap12WSAddressing10" />
      <httpTransport />
    </binding>

With an end point of:

  <endpoint address="http://destinationserver.com/SomeServiceName/V1"
    behaviorConfiguration="cliBeh" binding="customBinding" bindingConfiguration="MyServiceThatMustNotSendWSATO"
    contract="SomeContract.SomeMethod" name="SomeEndPointName">
    <identity>
      <dns value="somedns" />
    </identity>
  </endpoint>

I've tried all combinations of the textMessageEncoding messageVersion available, but the WSA:TO element is still generated :(

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">tns:ServiceEntryStatusOut_V1</a:Action>
        <a:MessageID>urn:uuid:88eda3c6-2b6a-4672-8e96-28f0e91c8b4c</a:MessageID>
        <a:RelatesTo>urn:uuid:1f19a9f3-6e46-47cc-b190-cc7ef71dbc67</a:RelatesTo>
        <a:To s:mustUnderstand="1">http://www.com/</a:To>
    </s:Header>

So in a nut shell, I need WS-Address fields such as Action, Message ID, RelatesTo, but NOT the To element.


Solution

  • Have you ever had one of those projects? Well, this question as part of a number of nightmare'ish problems I face. But in the end, I overcame them all and got a working solution out the door. It's not a nice solution, but it never was going to be so.

    Anyway in a nutshell to solve this particular issue I had to use a custom ClientMessageInspector class to remove the offending fields. That class is then part of a custom Behavior Extension. In my question I says that I don't want to do this - in my research this is simply not possible. You do need custom classes to override the default .NET SOAP handling classes.

    My custom inspector code ended up as follows:

    internal class ClientMessageInspector : IEndpointBehavior, IClientMessageInspector
    {
        public void Validate(ServiceEndpoint endpoint)
        {
        }
    
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            //throw new Exception("Exception in ApplyDispatchBehavior : ");
        }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            //throw new Exception("Exception in ApplyClientBehavior : ");
            clientRuntime.ClientMessageInspectors.Add(this);
        }
    
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            //throw new Exception("Exception in BeforeSendRequest : " + request.Headers.MessageId);
            var message = request;
            request = new MyCustomMessage(message);
            return null;
        }
    
        public class MyCustomMessage : Message
        {
            private readonly Message _message;
    
            public MyCustomMessage(Message message)
            {
                this._message = message;
            }
    
            protected override void OnWriteBodyContents(System.Xml.XmlDictionaryWriter writer)
            {
                MethodInfo dynMethod = _message.GetType().GetMethod("OnWriteBodyContents", BindingFlags.NonPublic | BindingFlags.Instance);
                dynMethod.Invoke(_message, new object[] { writer });
            }
    
            public override MessageHeaders Headers
            {
                get
                {
                    // Remove wsa:To header
                    var index = this._message.Headers.FindHeader("To", "http://www.w3.org/2005/08/addressing");
                    if (index > 0)
                    {
                        this._message.Headers.RemoveAt(index);
                    }
    
                    // Remove wsa:ReplyTo header
                    index = this._message.Headers.FindHeader("ReplyTo", "http://www.w3.org/2005/08/addressing");
                    if (index > 0)
                    {
                        this._message.Headers.RemoveAt(index);
                    }
    
                    // Remove VsDebuggerCausalityData (only appears in Dev but here from convenience)
                    index = this._message.Headers.FindHeader("VsDebuggerCausalityData", "http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink");
                    if (index > 0)
                    {
                        this._message.Headers.RemoveAt(index);
                    }
    
                    return this._message.Headers;
                }
            }
    
            public override MessageProperties Properties
            {
                get { return this._message.Properties; }
            }
    
            public override MessageVersion Version
            {
                get { return this._message.Version; }
            }
    
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            //throw new Exception("Exception in AfterReceiveReply : ");
        }
    }
    

    The binding required is complicated but ends up as follows. The key thing is to use the custom Behavior Extension to do the heavy lifting to remove those fields.

      <system.serviceModel>
    
        <extensions>
    
          <behaviorExtensions>
            <add name="myBehaviorExtensionElement"
                 type="MySecurityBE.MyBehaviorExtensionElement, MySecurityBE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
          </behaviorExtensions>
    
        </extensions>
    
        <bindings>
    
          <customBinding>
    
            <binding name="ServiceName_soap12" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" 
            sendTimeout="00:01:00" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" 
            maxReceivedMessageSize="65536" useDefaultWebProxy="true" allowCookies="false">
              <textMessageEncoding messageVersion="Soap12WSAddressing10" />
              <httpTransport maxReceivedMessageSize="2147483647" />
            </binding>
    
          </customBinding>
    
        </bindings>
    
        <behaviors>
    
          <endpointBehaviors>
            <behavior name="cliBeh">
              <myBehaviorExtensionElement/>
              <clientCredentials>
                <clientCertificate storeLocation="CurrentUser" storeName="My" x509FindType="FindBySubjectName" findValue="BradTestClientKey"/>
                <serviceCertificate>
                  <defaultCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" findValue="localhost2"/>
                  <authentication certificateValidationMode="None" trustedStoreLocation="LocalMachine"/>
                </serviceCertificate>
              </clientCredentials>
            </behavior>
          </endpointBehaviors>
    
        </behaviors>
    
        <client>
    
          <endpoint address="http://localhost.fiddler/TestRigClient_WS/Services/MyService"
                    binding="customBinding" bindingConfiguration="ServiceName_soap12"
                    contract="GenericFileTransferService.GenericFileTransfer"
                    name="ServiceName_soap12" behaviorConfiguration="cliBeh">
            <identity>
              <dns value="localhost2"/>
            </identity>
          </endpoint>
    
    
        </client>
    
        <diagnostics>
          <messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="false" maxMessagesToLog="30000000" maxSizeOfMessageToLog="2000000"/>
        </diagnostics>
    
      </system.serviceModel>
    

    I wish anyone facing the same battle good luck. Long live REST and may SOAP die a nice quiet death it so richly deserves (just my opinion after the nightmare year I spent on that project).