WCF and http (gzip/deflate) compression and Silverlight
The last couple of days I've tried to see if it was possible to make use of standard http compression on WCF services as I had very good experiences with WSE and even asmx webservices that used http compression to save bandwidth - but more important - and have better response times for large xml messages.
Tracing request/response with Fiddler
I created a small test project containing a contract and both a client and a service based on that contract and launched fiddler. I discovered that WCF does NOT add the Accept-Encoding: header with gzip and/or deflate values. So I fired the request again from fiddler but now I manually entered the Accept-Encoding header and the result was not compressed so I needed to first enable http compression in IIS.
Enable IIS http compression for WCF services
To enable http compression in IIS (6) you need to do the following:
- Launch the IIS mmc snapin
- Select the properties of Web sites
- Go to the Service tab
- Enable Compression application files
- Select OK
This enables http compression for dynamic content but now we need to let IIS know which dynamic content to compress. I will do this with the commandline adsutil.vbs script. Here I add http compression for .svc files and also change the default compression level from 0 to 9.
CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/GZIP/HcScriptFileExtensions "asp" "dll" "exe" "svc"
CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/DEFLATE/HcScriptFileExtensions "asp" "dll" "exe" "svc"
CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/GZIP/HcDynamicCompressionLevel 9
CSCRIPT.EXE ADSUTIL.VBS SET W3Svc/Filters/Compression/DEFLATE/HcDynamicCompressionLevel 9
After changing these settings you will need to restart IIS by launching iisreset.
Add Accept-Encoding http header to request
Here we have two possibilities. The first is the easiest by just adding the property to the operation context as shown below.
1: using (new OperationContextScope(client.InnerChannel))
3: var properties = new HttpRequestMessageProperty();
4: properties.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
5: //... call here
The second option is creating a client message inspector by inheriting from IClientMessageInspector and configure WCF to use the message inspector for a certain end-point. The message inspector would do exactly the same as the code above but then within the WCF pipeline. I will leave that as nice exercise ;-)
I launched my client and there I saw that the Accept-Encoding header was set in the request and that the result now got compressed by IIS but there WCF failed to decompress the result automatically so I started searching for a EnableDecompression like setting that web references have on the WCF client proxy and other WCF pipeline related settings but could not find a way to let WCF decompress the gzip/deflate response. The WCF technology samples contain a compression example and I modified it a bit. It uses gzip but that did not seem to work in fiddler so I used deflate in the example code and that worked and I also changed the code to NOT alter the content-type. Here I learned that you cannot change the transport properties in a MessageEncoder. But this is only needed when you want to compress your request or compress your response if you do not want IIS to do it for you (because you don't use IIS). So I used a message inspector implementing IClientMessageInspector and IDispatchMessageInspector to automatically set the AcceptEncoding and ContentEncoding http headers. This was working perfectly but I could not achieve to decompress the response on the server by first detecting the "ContentEncoding" header thus I used the work around to first try to decompress it and if it fails just try to process the request as normal. I also did this in the client pipeline and this also works.
I then tried to see the behavior of Silverlight as it delegates the http request to the browser to do the actual call and there I noticed that http compression works perfectly with Silverlight on a service reference. So the browser probably decompressed the response before Silverlight will get the data stream. Which accidentally is perfect for the scenarios in which we want to make use of http compression.
I now ditched this as we also need to support clients that do not set the AcceptEncoding header so I really need the ability to read the http header in request and set a "context" value to (not) compress the response and I have not found out yet how to do that per multiple concurrent requests. I really recommend to use the IIS http compression and not try to do this with WCF hacking as described here! If you want to compress your request than this is probably the only way to achieve this in the current WCF version.
If you want to make use of http compression in .net clients to decompress the response then just use a web reference instead of a service reference and make use of the EnableDecompression setting except when you use Silverlight as the client.