Developing a Custom TextMessageEncoder
Out of the box, Windows Communication Foundation is perfectly suited for simple WCF-to-WCF exchanges. However, the diverse communication requirements of most real world applications aren’t simple. For interactions with a high-availability client application, for instance, performance is paramount. Interoperability is straightforward, if the client and service are built on the .NET Framework. For communication with an existing Java EE-based application or with diverse partner applications, however, interoperability becomes the highest goal. Security requirements are also quite different, varying across connections with local Windows-based applications, a Java EE-based application running on another operating system, and a variety of partner applications coming in across the Internet. Even transactional requirements might vary, with only the internal applications being allowed to make transactional requests. How can these diverse business and technical requirements be met without exposing the creators of the new application to unmanageable complexity?
The answer to this question is WCF. Designed for exactly this kind of diverse but realistic scenario, WCF is the default technology for Windows applications that expose and access services. This three segment article introduces WCF, examining what it provides and showing how it’s used. The goal is to make clear what WCF is, show what problems it solves, and illustrate how WCF’s extensibility can help solve real world interoperability problems.
WCF is implemented primarily as a set of classes on top of the .NET Framework’s Common Language Runtime (CL R). Because it extends their familiar environment, WCF allows .NET developers to build service-oriented applications in a familiar way. As the figure below shows, WCF allows creating clients that access services. Both the client and the service can run in pretty much any Windows process—WCF doesn’t define a required host. Wherever they run, clients and services can interact via SOAP, via a WCF-specific binary protocol, or in some other way.
The secnario I’ll describe comes from a real-world WCF interoperability development challenge. I had to write a WCF client for a non-WCF SOAP11 service (Java). I had no control over the service which at lease had a minimal WSDL but no exposed metadata. The initial login request occurs over HTTPS with authenticationMode="MutualCertificate" using X509 certificates for both the client and the service endpoint.
The client is configured with a custom binding:
<textMessageEncoding messageVersion="Soap11" writeEncoding="utf-8" />
<localClientSettings detectReplays="false" reconnectTransportOnFailure="true" />
<httpsTransport keepAliveEnabled="false" requireClientCertificate="false" />
The service also requires that both the header and the body be signed. This requires modifying the message contract that was auto-generated from the WSDL Service Reference. Signing is accomplished by adding a ProtectionLe
vel statement to the MessageHeaderAttribute in the contract:
ProtectionLevel = System.Net.Security.ProtectionLevel.Sign
The service establishes a session for follow-on requests from the client and sends session related security tokens to the client in the login response to be used in subsequent requests (similar to a Security Token Service [STS] in a WS-Federation). The login response is valid; however, because the client isn’t expecting security tokens, the response causes a MessageSecurityException "Cannot find a token authenticator for the 'System.IdentityModel.Tokens.UserNameSecurityToken' token type. Tokens of that type cannot be accepted according to current security settings."
The response Security header element contains four tokens:
- SAML Assertion
- Binary Security Token
Now, we have the first requirement for WCF extensibility that enables interoperability – develop a custom text message handler to extract the unexpected security tokens. This MSDN sample - http://msdn.microsoft.com/en-us/library/ms751486 - fully describes how to develop a custom text message encoder. I used this sample and modified the ReadMessage method to extract and persist the security tokens:
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
byte msgContents = new byte[buffer.Count];
Array.Copy(buffer.Array, buffer.Offset, msgContents, 0, buffer.Count);
MemoryStream stream = new MemoryStream(msgContents);
stream = removeTokensFromStream(stream);
return ReadMessage(stream, int.MaxValue);
// used to persist & remove unexpected security tokens from the message
private MemoryStream removeTokensFromStream(MemoryStream message)
MemoryStream outputStream = new MemoryStream();
// remove the unexpected security header
message.Position = 0;
XElement xmlMessage = XElement.Load(message);
XNamespace sec = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
XNamespace saml = "urn:oasis:names:tc:SAML:1.0:assertion";
// find and persist the SAML Assertion
XElement samlAssertion = xmlMessage.Descendants(saml + "Assertion").First();
xmlMessage.Descendants(sec + "Security").Remove();
// save the modified message
MemoryStream outputMessage = new MemoryStream();
outputStream.Position = 0;
The response has now been modified to conform to what WCF expects from a message exchange that uses a custom binding wit h authenticationMode="MutualCertificate" for X509 certificates and is process without an exception. The service’s SAML Assertion is also saved to be used in a strong authentication scenario for follow-on requests. In the next part of this segment, I will discuss how we use the WriteMessage method in another custom TextMessageEncoder to add the SAML Assertion to follow-on service requests.