Wednesday, 10 November 2010

Consume a Soap with Attachments service from WCF

I'm putting this one out there now before I completely forget what I did.  One of our providers, in their *cough* infinite wisdom *cough* decided to deliver us some new functionality using Soap with Attachments.  On researching soap with attachments I found various sources that said the technology was dead - specified in 2004 and defunct as of 2006 in favour of MTOM.  It's now 2010 - They've delivered us something that's been officially dead for 4 years.  Sigh....
Now, MTOM is supposedly backward compatible with SWA (Soap with attachments) and WCF does support MTOM but it's impossible to guarantee that an unsupported technology that is supposedly supported won't break with changes to the WCF framework down the line.  That said, if anyone has got information about how to use an MTOM binding to send an attachment in which you can get the id of the attachment out then please let me know.
So, here's how to send SWA messages from WCF to a service not specified in .NET.
First off - go to codeplex and get the WCF Soap with attachments encoder (http://wcfswaencoder.codeplex.com/).
Now, include the project in your solution as you'll probably need to do some updates to the code to fix a bug or two.
In the project your using to call the SWA service from make sure you're using a WCF service reference rather than a web reference.
Add the following config entries to make sure you're using the appropriate encoder:


<configuration>
    <system.serviceModel>
      <!--
      WCF Extensions configuration section
    -->
      <extensions>
        <!--
        SOAP-With-Attachments custom encoder with custom mime parsing sample
        You need to perform this configuration manually after you have added a 
        Service Reference with Visual Studio or svcutil.exe from the .NET Framework SDK!
      -->
        <bindingElementExtensions>
          <add name="swaMessageEncoding"
               type="Microsoft.Austria.WcfHelpers.SoapWithAttachments.SwaMessageEncodingElement, Microsoft.Austria.WcfHelpers.SoapWithAttachments" />
        </bindingElementExtensions>
      </extensions>
      
        <bindings>
            <customBinding>
              <binding name="SwaBindingConfiguration">
                <swaMessageEncoding innerMessageEncoding="textMessageEncoding" />
                <httpTransport manualAddressing="false" maxBufferPoolSize="524288"
                        maxReceivedMessageSize="62914560" allowCookies="false" authenticationScheme="Anonymous"
                        bypassProxyOnLocal="false" decompressionEnabled="true" hostNameComparisonMode="StrongWildcard"
                        keepAliveEnabled="true" maxBufferSize="62914560" proxyAuthenticationScheme="Anonymous"
                        realm="" transferMode="Streamed" unsafeConnectionNtlmAuthentication="false"
                        useDefaultWebProxy="true">
                  <!--<extendedProtectionPolicy policyEnforcement="Never" />-->
                </httpTransport>
              </binding>
            </customBinding>
        </bindings>
        <client>
           <endpoint address="http://xyz.zyx.com"
                binding="customBinding" bindingConfiguration="SwaBindingConfiguration"
                contract="ServiceRef.Contract" name="MyEndpoint" />
        </client>
    </system.serviceModel>
</configuration>

Now, once you've set up the client using something like ServiceRef.Contract client = new ServiceRef.Contract("MyEndpoint"); you'll need to wrap all your operations in a using statement that gives the OperationContext like so:

using (OperationContextScope Scope = new OperationContextScope(client.InnerChannel))
            {
                ServiceRef.WSLoginParamsDTO loginDTO = new ServiceRef.WSLoginParamsDTO();
                ...
                sessionId = client.login(loginDTO);
            }

That applies to all operations against this client.

Now, you can add an attachment to the message by doing the following:

using (OperationContextScope Scope = new OperationContextScope(client.InnerChannel))
            {
                OperationContext.Current.OutgoingMessageProperties.Add
                    (
                        Microsoft.Austria.WcfHelpers.SoapWithAttachments.SwaEncoderConstants.AttachmentProperty,
                        AttachmentContents
                    );


                Console.WriteLine("Calling the service...");
                ServiceRef.WSCreateJobImageParamsDTO createJobImageParameters = new ServiceRef.WSCreateJobImageParamsDTO();
                ...
// This bit might not be required, depending on your web service
                createJobImageParameters.imageId = "UZE_26123_";
                ...
                bool success = client.addJobImage(createJobImageParameters);
}


Personally, I also had to make a change to the Microsoft helper library itself.  In SwaEncoder the WriteMessage function needed to be changed so the line:
 _SoapMimeContent.Content = Encoding.UTF8.GetBytes(message.GetBody<string>());
became
_SoapMimeContent.Content = Encoding.UTF8.GetBytes(message.ToString());

If you're still struggling, you should get yourself two tools that will let you debug the messages that are going over the wire.  The first is Soap-UI, which lets you add attachments to messages and test the way it should work (yes, it's a java tool but it's really useful):
Add the attachment by clicking the Attachments button at the button, add an attachment then specify the Content ID for it if you need to reference it in the request.
Once you've submitted a successful request you can use the Raw tab at the side of the request to see exactly what went over the wire.

That shows you what the SOAP should look like.  Now, to see what you're actually sending in your app use Wireshark, start tracing on your network interface and send the message.  You can then follow the TCP conversation by finding the IP address you're supposed to be communicating with, right clicking on the packet and clicking "Follow TCP stream":

Hope that helped!

2 comments:

  1. Hi, I have a requirement to receive an attachment from a java service which was implemented using Soap with Attachments. The codeplex link in you post states that the encoding needs to be configured on both service and client end. I cannot change the Java service. Do you think I would be able to use the encoder to receive the attachment?

    ReplyDelete
  2. Hi Adam,
    Thanks for your article. I can create a client but i can't create the service with swaMessageEncoding (i get an error because OperationContext.current is null when my service is called).
    Thanks,
    Paolo

    ReplyDelete