ompression has become an integral design step for almost any modern programming design, because serialized data compression is a well-supported and logical step toward significant performance gains, and reducing the costs of network traffic.
There are several methods in use that enable seamless compression, all of which can be difficult to get up and running successfully:
- Building proxy factories to handle data compression and decompression of data on both the client and service sides
- Adding server-side code that converts an entity such as a DataSet to a compressed byte array after it's been serialized and just before it gets sent through the wire.
- Doing the reverse on the client side—adding decompression code to turn the compressed byte array back into the predefined type
- Custom code that checks whether the server returned a compressed response before trying to decompress it on the client, as shown in the following example:
Stream responseStream = WebResponse.GetResponseStream();
responseStream = new GZipStream(responseStream,
responseStream = new DeflateStream(
Terms and Classes
Before jumping into the article, it's worth defining the terms commonly used in dealing with messaging and encoding:
- Message: An object that contains request and response data.
- Binding: A specification that defines how data should be transmitted across endpoints. It includes the protocol, message encoder, and endpoints.
- Endpoint: A receiving/transmitting port.
- Message Encoder: A message encoder serializes a message instance into bytes. Which message encoder a given message should use is defined in the message encoding binding element. As shipped, WCF includes three message encoders: Binary, Text, and MTOM (Message Transmission Optimization Mechanism). The message encoder first serializes the outgoing message, then applies custom behaviors, and finally, passes the serialized message to the transport layer. The message encoder at the other end receives the serialized message from the transport layer, performs any custom behaviors, and then passes the deserialized message to the protocol layer/caller application.
Although message encoders reside in the transport layer in the transmitting channel and even when they format the messages as intended, they may not work for all clients. Fortunately, you can overcome this problem easily using WCF endpoints. You can design multiple endpoints that cater to different client needs. Therefore, you can use multiple encoders (with or without compression) and let the clients determine which they would prefer to use based on their requirements.
The ideal solution is one in which the channels at both the ends of the WCF communication recognize whether the data is compressed, and if so, can successfully decompress the compressed data, permitting seamless and flawless transport.
One interesting point is that the custom encoding process is so transparent that the core WCF services developers never even knew about such a mechanism being wrapped around their services—the custom encoder simply hooks onto both the client and server sides.
The following binding elements derive from the MessageEncodingBindingElement class that provides the Binary, Text, and MTOM encoding.
These binding elements create a MessageEncoderFactory that in turn creates singleton MessageEncoder instances to serialize messages into bytes.
To use a message encoder, you call the ReadMessage and WriteMessage methods to read from or write to a stream or byte array. Therefore, to write a custom encoder, you need to either extend an existing message encoder or provide a custom implementation of a few Message base classes: MessageEncoder, MessageEncoderFactory, and the MessageEncodingBindingElement.
- MessageEncoder serializes transmitted messages in the WriteMessage method and deserializes incoming messages in the ReadMessage method. You must override these two methods to plug in a custom implementation. Typically, these inhabit Transport channels, but can be used elsewhere in the channel stack.
- MessageEncoderFactory lets you specify the contract information used to configure the services. You need to override the CreateMessageEncoderFactory method in this abstract class.
- MessageEncodingBindingElement supports adding an encoding to the binding element.
To fully integrate a custom encoder into WCF services, you also need to ensure that you have full control over the encoder itself, including:
- A switch that enables/disables compression on the WCF requests; client-side configuration is the best place for this.
- A switch to enable/disable compression of WCF responses; server-side configuration is be the best location.
- A key that denotes the custom compression encoder
- Possibly extending WCF to support various SOAP-based interactions—even multiple versions. For example, you might need to support clients still using the .NET Framework 1.1 using your WCF Services.
- A simple mechanism to determine whether incoming data is compressed to ensure faster responses and avoid errors.