Writing a .NET client for a third-party SOAP web service is relatively simple and straightforward task. Web is full of tutorials or how-to-examples which will help you in case you are new to this field. The first step is generation of proxy .NET class via WSDL.exe utility and its implementation into your project. After that you can simply start using remote resources with all of the cool stuff. Of course, you can always add a WCF service reference (as commonly suggested on many forums), however, sometime this approach cannot be used due to technology limitation or simply you just don’t want to waste your time with setting-up a secured WCF client.

The story behind

Recently we received access to a third-party Java web service offering us some awesome features we wished to implement into our .NET client application. We were provided with a WSDL web service specification and some schema definition files. We simply generated C# proxy classes and started to play with all those new awesome features. After a while we noticed that some of the provided methods are missing in our proxy class. We were sniffing around and found following comment in the proxy class:

// CODEGEN: The operation binding ‘AwesomeMethod’ from namespace ‘http://awesome.namespace/wsdl’ was ignored.  Missing soap:body input binding.

The wsdl utility strangely did not created some of the proxy methods. What happend? Well, all the missing methods actually served for uploading files to the remote server via multipart/related MIME bindings. In WSDL specification it was given as following:
<wsdl:input name="AwesomeMethodInput">
     <mime:multipartRelated>
          <mime:part>
               <soap:body use="literal" part="awesomeMethod"/>
          </mime:part>
          <mime:part>
               <mime:content part="uploadFile" type="application/octet-stream"/>
          </mime:part>
     </mime:multipartRelated>
</wsdl:input> 
Despite MIME attachments are standard part of SOAP specification (see http://www.w3.org/TR/SOAP-attachments), the Microsoft implementation do not support any type of multipart MIME messages (instead he created own standard called DIME, see http://en.wikipedia.org/wiki/Direct_Internet_Message_Encapsulation). Therefore we had to select different solution.

SOAP web client

The least painful option left for us was to implement the client directly through System.Net library. In fact, writing a web client in C# is not essentially difficult and plenty of examples can be found all over the internet:
var request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = "text/xml; charset=utf-8";
request.Method = "POST";
request.Timeout = timeout;
request.Credentials = new NetworkCredential("username""password");
using (var stream = request.GetRequestStream())
{
    using (var writer = new StreamWriter(stream))
    {
        writer.Write(postData);
    }
}
Using such example one can easily connect to any http based network resource. Connecting to a SOAP web service is not difficult at all either – in contrast to a simple http web client you have to add only two more things:

 

1) Specify your SOAP action in request header:

request.Headers.Add("SoapAction"@"""awesome.namespace#AwesomeMethod""");

As you can see, the action name consists of method namespace with method name, both delimited by hash. The specification must be wraped in quotation marks otherwise the request will not be performed.

2) Construct a SOAP request according to specification and append it to POST body according to your WSDL specification:

var requestString =  @"<? xml version=""1.0"" encoding=""utf-8"" ?>
<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope"">
<soap:Body>
     <message-content/>
</soap:Body>
</soap:Envelope>"
;
Follow your WSDL specification carefully and you will be rewarded with working sample of SOAP client. Of course, you have to parse responses manually in that case, however, it is not such an issue in case you have no other option to call a web service.

How to attach file

If you want to upload a file to a remote SOAP web service, you have to send its content as a part of multipart MIME message. There are many multipart content types which can be used; probably the most common is multipart/form-data used for sending data from web-forms. In our case, the request had to be sent as multipart/related content according to the specification, therefore in the following we will focus on construction of such requests.

First of all, you have to specify appropriate content type and boundary in your request header. The boundary is essential part of every multipart content type, it is any string which delimits single parts of the message and denotes end of the message. The header should be specified as following:

request.ContentType = @"multipart/related; boundary=some_boundary; charset=utf-8; type=text/xml; start=""<first-part>""";

At first you can see specification of our content type followed by definition of boundary (quotation marks must not be used, no white spaces are allowed). Attribute start defines the first part of the message itself through its Content-ID; for multipart/related content type, this attribute must be specified and wrapped in quotation marks. So, if you have the header specified, the message construction with uploaded file is quite simple. You just have to compose following request message and set it as your request body:

--some_boundary
Content-ID: <first-part>
Content-Type: text/xml; charset=utf-8
   
<?xml version='1.0'?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <message-content/>
</soap:Envelope>
--some_boundary
Content-ID: <second-part>
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

...@a!+s1dw32w16qw...your-binary-file…s+61...
--some_boundary--

The specification is quite strict, therefore the request must be constructed exactly as you see it in the example. I mean, boundaries must be prefixed with a double-dash. The footer of whole message must be in suffixed with another double-dash, each header must be on a new line, headers and content itself must be delimited with CRLF constant and so on.

Note that Content-Transfer-Encoding header specifies how the uploaded file will be transferred – for example you can specify binary, base64, utf-8, etc.. This value might depend on specification of your web service. It also determines the way how you will treat content of uploaded file before you attach it to the request body. For example, in case of base64 transfer encoding you have to encode whole file into base64. If you set transfer encoding to binary, you should use BinaryWriter instead of StreamWriter for uploading request body data into your request stream otherwise the uploaded file might be corrupted in your request.

Conclusion

Now you should be able to create your own SOAP requests with multipart/related MIME attachments. We could continue with more and more examples (e.g. with references between message parts), the options are countless. If you want to know more, you can read SOAP attachment specification at http://www.w3.org/TR/SOAP-attachments – there is lot of text, however, if you scroll down you can find plenty of great examples which can suite your problem.