SOAP & WSDL
The job description lists SOAP alongside REST, which usually means an existing enterprise system to integrate with. Know the message shape and the WSDL structure cold.
What is SOAP?
An XML-based messaging protocol with a formal contract (WSDL) describing every operation and type — usually sent over HTTP POST.
| Property | Meaning |
|---|---|
| A protocol, not a style | SOAP has rules; REST is an architectural style |
| XML only | No JSON — the payload is always XML |
| Contract-first | The WSDL defines everything before anyone writes a client |
| Transport-independent | HTTP is normal, but SMTP, TCP and JMS are all valid |
| Extensible | WS-* adds security, reliable delivery and transactions |
Where you still meet it: banking and payments, telecom provisioning, government and tax systems, insurance, ERP (SAP, Dynamics), and older healthcare integrations — anywhere a formal, signed contract between organisations matters more than payload size.
SOAP is an XML messaging protocol with a formal contract. The service publishes a WSDL that
describes every operation, every type and the endpoint, and a client generates a strongly typed
proxy from it. Messages are XML envelopes, normally over HTTP POST, though SOAP itself is
transport-independent. Its strength is the strict contract plus the WS-* standards for
security, reliable messaging and transactions, which is why it persists in banking, telecom
and government systems long after REST took over the public web.
Envelope, Header, Body, Fault
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header> <!-- OPTIONAL: metadata -->
<wsse:Security soap:mustUnderstand="1">
<wsse:UsernameToken>
<wsse:Username>svc_orders</wsse:Username>
<wsse:Password>***</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body> <!-- REQUIRED: the actual payload -->
<GetCustomer xmlns="http://tempuri.org/">
<CustomerId>4821</CustomerId>
</GetCustomer>
</soap:Body>
</soap:Envelope>
| Element | Required | Carries |
|---|---|---|
| Envelope | ✅ | The root — marks the XML as a SOAP message |
| Header | ❌ | Security tokens, transaction ids, routing, correlation |
| Body | ✅ | The request or response data |
| Fault | ❌ | Errors — and it lives inside the Body |
mustUnderstand="1"
A header marked this way must be processed by the receiver. If it can't, it is required to return a fault rather than silently ignoring it — which is how a security header can't be quietly dropped.
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Customer 4821 was not found</faultstring>
<detail><ErrorCode>CUST_NOT_FOUND</ErrorCode></detail>
</soap:Fault>
</soap:Body>
| SOAP 1.1 | SOAP 1.2 | Meaning |
|---|---|---|
Client | Sender | The caller's fault — don't retry unchanged |
Server | Receiver | The service's fault — a retry may help |
VersionMismatch | Same | Wrong envelope namespace |
MustUnderstand | Same | A required header couldn't be processed |
A fault travels with HTTP 500 — so "500" from a SOAP service usually means a business fault, not a crash. Read the fault body before assuming an outage.
SOAP 1.1 vs SOAP 1.2
| SOAP 1.1 | SOAP 1.2 | |
|---|---|---|
| Namespace | schemas.xmlsoap.org/soap/envelope/ | www.w3.org/2003/05/soap-envelope |
| Content-Type | text/xml | application/soap+xml |
| The action | Separate SOAPAction HTTP header | action parameter inside Content-Type |
| Fault codes | Client / Server | Sender / Receiver |
| WCF binding | basicHttpBinding | wsHttpBinding |
| In practice | Most widely deployed, best interop | The formal W3C standard |
Send a 1.1 envelope to a 1.2 endpoint and you get a VersionMismatch fault or an
unhelpful 500. If a service "doesn't work" and the XML looks right, check the namespace and the
content type before anything else.
What is a WSDL, and what are its parts?
The XML contract for the service — retrieved by appending ?wsdl
to the endpoint — describing the types, the operations, how to call them and where they live.
<portType name="ICustomerService">
<operation name="GetCustomer">
<input message="tns:GetCustomerRequest"/>
<output message="tns:GetCustomerResponse"/>
</operation>
</portType>
<binding name="BasicHttpBinding_ICustomerService" type="tns:ICustomerService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="GetCustomer">
<soap:operation soapAction="http://tempuri.org/ICustomerService/GetCustomer"/>
</operation>
</binding>
<service name="CustomerService">
<port name="BasicHttpBinding_ICustomerService" binding="tns:BasicHttpBinding_ICustomerService">
<soap:address location="https://legacy.acme.com/CustomerService.svc"/>
</port>
</service>
That's why a proxy generator needs the whole WSDL: portType alone tells you the method exists, but not how or where to call it.
# .NET Core / .NET 8
dotnet tool install --global dotnet-svcutil
dotnet-svcutil https://legacy.acme.com/CustomerService.svc?wsdl
# .NET Framework
svcutil.exe https://legacy.acme.com/CustomerService.svc?wsdl
# or Visual Studio → Add Connected Service → WCF Web Service Reference
The tool generates a proxy class plus the data classes, so calling the remote service looks like calling a local object. The important discipline: regenerate when the contract changes, and keep the generated file out of manual edits — the WSDL is the source of truth.
| document/literal (wrapped) | RPC/encoded | |
|---|---|---|
| Body contains | An XML document validated against an XSD | A method name with typed parameters |
| Schema validation | ✅ Full | ❌ Encoding rules instead |
| WS-I compliant | ✅ | ❌ |
| Status | The modern default | Legacy — you'll only meet it in very old services |
The style is declared in the WSDL's <soap:binding style="document">. If you're
ever asked which to choose: document/literal wrapped, because it validates and
it interoperates.
POST /CustomerService.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/ICustomerService/GetCustomer"
It tells the server which operation the message is for, so it can route without parsing the body.
In SOAP 1.2 it moves into the content type:
application/soap+xml; action="...".
Practical value: a missing or mistyped SOAPAction is one of the most common
causes of an unexplained 500 when hand-crafting requests — the exact value comes from the
<soap:operation soapAction="…"> in the WSDL.
Message Transmission Optimization Mechanism — binary attachments travel as separate MIME parts instead of base64 text inside the XML.
Base64 inflates data by roughly 33% and forces the whole document through the XML parser. Enable
MTOM on both sides (in WCF, messageEncoding="Mtom"), and combine it
with streaming for genuinely large files.
Universal Description, Discovery and Integration — a registry where services would be published and discovered automatically. It was part of the original "publish, find, bind" vision and is effectively dead; the public registries were shut down in 2006.
The honest answer in an interview: "It was the discovery piece of the original web services stack, but in practice nobody uses it — services are integrated by being given the WSDL URL, and internally by an API catalogue or API Management."
Stateless by default — each message is independent. WS-* changes that when you
need it: WS-ReliableMessaging for guaranteed, ordered delivery and
WS-AtomicTransaction for distributed transactions, both of which introduce
conversation state.
Transport-independent — HTTP is by far the most common, but SOAP over SMTP, TCP
and JMS all exist. In WCF that choice is just the binding: the same contract runs over
basicHttpBinding or netTcpBinding with no code change, which is one of
the genuinely elegant parts of the design.