Build a Custom WCF Encoder

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();if (WebResponse.ContentEncoding.ToLower().Contains("gzip"))   responseStream = new GZipStream(responseStream,       CompressionMode.Decompress);else if(WebResponse.ContentEncoding.ToLower().Contains("deflate"))   responseStream = new DeflateStream(      responseStream,CompressionMode.Decompress);

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.

Creating a Custom Encoder

To begin, you need to create a custom MessageEncoderFactory, which creates your custom encoder object. It needs:

  • An overridden encoder object
  • An overridden message version

Listing 1 contains the code for an example class named CustomEncoderFactory created from the CustomMessageEncoder factory. You need to mark the Encoder as a singleton object of the CustomMessageEncoder factory.

To create a CustomEncoderFactory instance, you need to pass in two new things: an EncodeMode enumeration value, and an EnableCompression variable:

  • EncodeMode is an enum that supports dynamically changing the encoding format based on configuration values, and writing compression/decompression logic without relying on advance knowledge of any particular Encoder. The enum shown below supports various compression types including None, Deflate, Gzip, and you could easily add many more custom compression encoder formats in the future:
   ///    /// Compression Encoder formats. Add custom encoders such as    /// ICSharpLib, 7z, rar    ///    public enum CompressionEncoder   {      None,      Deflate,      GZip   }
  • EnableCompression is a Boolean switch value that lets you enable or disable compression as discussed earlier.

Next, you need to create a CustomEncoder that implements the abstract MessageEncoder class (see Listing 2). The example in Listing 2 implements an IsDataCompressed Boolean method that provides an easy way to determine if the data is compressed. For Gzip, you can use the “magic code” value (see the GZIP_MAGIC constant in Listing 2) to determine whether the data is compressed.

As mentioned earlier, you’ll see that the encoding process in this custom encoder takes place at in the ReadMessage and WriteMessage methods. You also need to override the ContentType property to deliver different content types. The CompressionEncoder enum value determines the content type at runtime.

Next, you need to create a CustomMessageEncodingBinding Element that lets you specify the configurable custom properties, which in this case include EnableCompression, CompressionEncoder, and the binding element. Listing 3 contains the code.

Lastly, you need to create a CustomMessageEncodingElement. This element derives from the BindingElementExtensionElement class (see Listing 4).

Note: the use of the ConfigurationPropertyAttribute?it helps to map the attributes to the properties.

After the values have been read from the configuration file, the CreateBindingElement method acts as an entry point and serves to convert the values read into suitable properties of the Custom Binding Element.

The key points to note are:

  • The CreateBindingElement method, which acts as an entry point.
  • Author’s Note: You could alter the messageversion property of the binding element through configuration, but for simplicity, that process is not shown here.

  • The ApplyConfiguration method which lets you explicitly indicate properties such as:
  • ReaderQuotas, which are used to assign properties to the CustomMesssageEncodingBindingElement.
  • The ReaderQuotas.MaxArrayLength value controls the request size. Because this example uses custom binding, you need to set it to the binding element.

To conclude, you need to implement the configuration sections for the CustomBindingElement. On the client, the configuration looks like this:

                                                                        

The custom binding created above uses the new CustomMessageEncoding. Also note that the httpTransport inner element currently represents only BasicHttpBinding, but could be adapted to work with future needs. Requests do not usually need to be compressed, as they tend to be small; in fact, compressing them often increases the request size. Hence, the client configuration file shown above uses the enableCompression="false" setting.

The server configuration would look like this:

                                                                                                      endpoint address=""       binding="customBinding"       bindingConfiguration="myBinding"      contract="ServiceContracts.IService" />            

By following the procedures described in this article, you’ll find that it’s both straightforward and transparent to add custom encoding to your WCF messaging applications. Be sure to download the provided sample code, which contains the full source for all the classes described in this article as well as the configuration sections you’ll need.

References

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: