Handling Compression (Accept-Encoding) Sample

Long time back there was a question on forums regarding how we could achieve compression scenario via Accept-Encoding headers in Web API. Thought of bringing that sample here so that it could be useful for anyone looking for this kind of functionality.

NOTE: ASP.NET Web API doesn’t have inherent support for Accept-Encoding header.

Let’s assume we have the following controller which is returning 3 different HttpContents: StringContent, StreamContent & ObjectContent.

 public class AcceptEncodingTestsController : ApiController
    {
        [HttpGet]
        public HttpResponseMessage GetStringContent()
        {
            HttpResponseMessage response = new HttpResponseMessage();
            response.Content = new StringContent("Hello World!");

            return response;
        }

        [HttpGet]
        public HttpResponseMessage GetStreamContent()
        {
            HttpResponseMessage response = new HttpResponseMessage();
            response.Content = new StreamContent(new FileStream(@"D\Sample.txt", FileMode.Open));
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
            return response;
        }

        [HttpGet]
        public Customer GetCustomer() // the return value is actually converted to ObjectContent
        {
            return new Customer() { Id = 100, DateOfBirth = new DateTime(1975, 4, 3) };
        }
    }

 

Encoding handler:

 public class EncodingDelegateHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
            {
                HttpResponseMessage response = responseToCompleteTask.Result;

                if (response.RequestMessage.Headers.AcceptEncoding != null &&
                    response.RequestMessage.Headers.AcceptEncoding.Count > 0)
                {
                    string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value;

                    response.Content = new CompressedContent(response.Content, encodingType);
                }

                return response;
            },
            TaskContinuationOptions.OnlyOnRanToCompletion);
        }
    }

    public class CompressedContent : HttpContent
    {
        private HttpContent originalContent;
        private string encodingType;

        public CompressedContent(HttpContent content, string encodingType)
        {
            if (content == null)
            {
                throw new ArgumentNullException("content");
            }

            if (encodingType == null)
            {
                throw new ArgumentNullException("encodingType");
            }

            originalContent = content;
            this.encodingType = encodingType.ToLowerInvariant();

            if (this.encodingType != "gzip" && this.encodingType != "deflate")
            {
                throw new InvalidOperationException(string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this.encodingType));
            }

            // copy the headers from the original content
            foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
            {
                this.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            this.Headers.ContentEncoding.Add(encodingType);
        }

        protected override bool TryComputeLength(out long length)
        {
            length = -1;

            return false;
        }

        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            Stream compressedStream = null;

            if (encodingType == "gzip")
            {
                compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
            }
            else if (encodingType == "deflate")
            {
                compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
            }

            return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
            {
                if (compressedStream != null)
                {
                    compressedStream.Dispose();
                }
            });
        }
    }

Register the handler:

 config.MessageHandlers.Add(new EncodingDelegateHandler());

 

With the above example you should see that all the 3 types of contents are compressed via the encoding handler.