Introduction
There are many articles about how WCF works, so I'll skip that part in this article. The purpose of this piece of code is to enable the user to compress the data transferred between machines using the WCF. (For simplicity, I am using the term WCF instead of referring to specific components.)
When the WCF is used to communicate between two entities, it creates a channel of communication to transfer and then transfers messages. The messages contain the data of requests and responses. WCF uses special encoders to translate the request and responses data into an array of bytes.
The CompactMessageEncoder
hooks into the channel on both sides (Client & Server). When a message is transferred, the encoder compresses it on the sending side and decompress it on the receiving side, hence it is transparent.
Background
My drive to implement such an encoder was the need to transfer a big file (XML format) from one machine to another, where the connection has a speed limit.
I decided to do this with WCF for simplicity (After the transfer, there were more things to do). I didn't want to write a special function that compresses on one side and uncompresses on the other, but rather configure the channel itself to do this. This way the compression can be reused on any contract.
After picking and poking in the Internet, I found out that writing a message encoder is the simplest way. The real problem was how to write a message encoder, since the MSDN doesn't have any example (at least I didn't find any). Any example I found was out of date since they were written before the WCF was finally released. However these examples gave me some firm ground of how to write a message encoder.
My implementation is based on an example that I got installed on my machine, but I don't know its origin. I found it at C:\Program Files\Microsoft SDKs\WinFX\samples\Allsamples\Indigo\TechnologySamples\Extensibility\
Channels\MessageEncoder\Compression\CS. The example is exactly what I wanted to have in the first place, unfortunately it doesn't work. It is written for previous versions of WCF and includes interfaces and classes that no longer exist. From this example, I got the knowledge of how to compress and uncompress the message. I also noticed that the implementation had two minor bugs, so I fixed them.
I had some help from Nicholas Allen's blog at http://blogs.msdn.com/drnick/archive/2006/05/09/592933.aspx. However, it doesn't contain code on how to add the message encoder to the configuration file.
I also used the Reflector to see how other message encoders work such as:
System.ServiceModel.Channels.BinaryMessageEncoderFactory.BinaryMessageEncoder.
How It Works
The idea of the compact message encoder is to hook the sending and receiving of messages on the channel and compress them on the sender and then decompress them on the receiver. To implant the message encoder, a binding element is added into a customBinding
.
Since the compression doesn't have to do the encoding itself, it uses another message encoder to do this for it, for example, the BinaryMessageEncoder
.
Normal Service Method Execution
Sender side:
- A method is called in code.
- The method and its parameters are serialized into a SOAP message.
- The message encoder serializes the message into a bytes array.
- The bytes array are sent via the transport layer.
Receiver side:
- The transport layer receives a bytes array.
- The message encoder deserialized the bytes array into a message.
- The method and its parameters are deserialized into a SOAP message.
- The real method is called.
When the compact message encoder is added, the method call is changed a bit:
Sender side:
- A method is called in code.
- The method and its parameters are serialized into a SOAP message.
- The compact message encoder lets its inner message encoder to serialize the message into a bytes array.
- The compact message encoder compresses the bytes array into a second bytes array.
- The bytes array are sent via the transport layer.
Receiver side:
- The transport layer receives bytes array.
- The compact message encoder decompresses the bytes array into a second bytes array.
- The compact message encoder let its inner message encoder to deserialize the second bytes array into a message.
- The method and its parameters are deserialized into a SOAP message.
- The real method is called.
The compact message encoder is divided to several classes:
CompactMessageEncoder
- This class provides the message encoder implementation.
CompactMessageEncoderFactory
- This class is responsible to provide the Compact message encoder instance.
CompactMessageEncodingBindingElement
- This is the class that participates in the binding stack of the channel.
CompactMessageEncodingElement
- This is the class that enables the message encoder to be added via the application configuration file.
Compression
The CompactMessageEncoder
uses the GZip compression which is implemented within the .NET Framework. This is implemented with System.IO.Compression.GZipStream
.
How To Use
Add Reference to the CompactMessageEncoder.dll
Before changing the app.config, you must add a reference to the CompactMessageEncoder.dll. This must be done on both Client and Server applications.
Server config Change
This is an example of app.config before adding the compact message encoder:
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<services>
<service name="Server.MyService">
<endpoint
address="net.tcp://localhost:1234/MyService"
binding="netTcpBinding"
contract="Server.IMyService" />
</service>
</services>
</system.serviceModel>
</configuration>
This is an example of the app.config after adding the compact message encoder:
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<services>
<service name="Server.MyService">
<endpoint
address="net.tcp://localhost:1234/MyService"
binding="customBinding"
contract="Server.IMyService" />
</service>
</services>
<bindings>
<customBinding>
<binding name="compactBinding">
<compactMessageEncoding>
<binaryMessageEncoding />
</compactMessageEncoding>
<tcpTransport />
</binding>
</customBinding>
</bindings>
<extensions>
<bindingElementExtensions>
<add name="compactMessageEncoding"
type="Amib.WCF.CompactMessageEncodingElement,
CompactMessageEncoder, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>
</system.serviceModel>
</configuration>
Client config Change
This is an example of app.config before adding the compact message encoder:
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<client>
<endpoint
address="net.tcp://localhost:1234/MyService"
binding="customBinding"
bindingConfiguration="compactBinding"
contract="Client.IMyService" />
</client>
</system.serviceModel>
</configuration>
This is an example of the app.config after adding the compact message encoder:
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<client>
<endpoint
address="net.tcp://localhost:1234/MyService"
binding="customBinding"
bindingConfiguration="compactBinding"
contract="Client.IMyService" />
</client>
<bindings>
<customBinding>
<binding name="compactBinding">
<compactMessageEncoding>
<binaryMessageEncoding/>
</compactMessageEncoding>
<tcpTransport />
</binding>
</customBinding>
</bindings>
<extensions>
<bindingElementExtensions>
<add name="compactMessageEncoding"
type="Amib.WCF.CompactMessageEncodingElement,
CompactMessageEncoder, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>
</system.serviceModel>
</configuration>
Limitations & Workarounds
- All messages are compressed on the channel even if it will cause the message to inflate. This happens when the message is small.
- The
CompactMessageEncoder
supports only Buffered transport and not Strearmed transport. - Using the
CompactMessageEncoder
on client and server that run on the same machine may downgrade performance. - The WCF configuration editor works partially with the
CompactMessageEncoder
, therefore part of the configuration must be done manually with the XML text editor. - Each time the WCF configuration editor opens the app.config file, it asks about the safety of the CompactMessageEncoder.dll. I don't know how to get rid of this behavior.
- The configuration of the
binaryMessageEncoding
and the textMessageEncoding
cannot be edited within the WCF configuration editor. In order to overcome this, remove the compactMessageEncoding
element from the app.config (leaving its children), and then open it with the WCF configuration editor. Afterwards, add the compactMessageEncoding
element back.
Disclaimer
THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
- 5th September, 2007: Initial version