I am currently working on migrating few of my MVC3 Controllers to MVC4 Api Controllers.
I have implemented Compression mechanism for MVC3 controller Get Method Responses by inherting ActionFilterAttribute
and overriding OnActionExecutiong
method. After some Research I found that I need to use ActionFilterMethod
from System.Web.HttpFilters
. It would be great if somebody can share piece of sample code to get me started for this compressing HTTP response using GZip
Asked
Active
Viewed 2.0k times
17

abatishchev
- 98,240
- 88
- 296
- 433

Pavan Josyula
- 1,355
- 3
- 13
- 25
-
I'm having the same problem, although in my case I already enabled IIS compression. In your case, was it the IIS compression, or did you create the custom handler? – Carvellis Jun 08 '12 at 13:11
-
Yes, I have used custom handler for this just like the way Darin mentioned here. – Pavan Josyula Jun 10 '12 at 09:14
3 Answers
40
The easiest is to enable compression directly at IIS level.
If you want to do it at the application level you could write a custom delegating message handler as shown in the following post:
public class CompressHandler : 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)
{
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.AddWithoutValidation(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();
}
});
}
}
All that's left now is to register the handler in Application_Start
:
GlobalConfiguration.Configuration.MessageHandlers.Add(new CompressHandler());

Darin Dimitrov
- 1,023,142
- 271
- 3,287
- 2,928
-
I think there's a bug in this code (as well as in similar examples found on the web): The Content-Length Header is set incorrectly because the Content-Length Header is copied from the gzipped content. This can be easily reproduced by passing a StringContent through the Compression Handler. To fix this, the line with `originalContent.Headers` needs to be fixed like this: `originalContent.Headers.Where(x => x.Key != "Content-Length")` – Johannes Rudolph Apr 05 '13 at 12:01
-
Code will fail if no Accept-Encoding is provided. `if (response.RequestMessage.Headers.AcceptEncoding != null)` should be `if (response.RequestMessage.Headers.AcceptEncoding.Any())` – Jan Sommer Oct 06 '13 at 11:50
-
I'd recommend adding the following in SendAsync between the assignment of encodingType and assignment of response.Content to allow error responses to return without compression `if (response.StatusCode != HttpStatusCode.OK || response.Content == null || string.IsNullOrWhiteSpace(encodingType)) return response;` – Paul Jul 17 '14 at 13:22
-
I needed to replace the AcceptEncoding check with the following code: if (response.RequestMessage.Headers.AcceptEncoding.Any()) { string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value; if (response.Content != null) { response.Content = new CompressedContent(response.Content, encodingType); } } – mike gold Sep 24 '14 at 20:44
-
How would you incorporate response.Content.LoadIntoBufferAsync() to get the length of the response content (response.Content.Headers.ContentLength) and then exclude the result from zipping if it is smaller then some threshold? When the line above is added before setting response.Content, the call ends in an timeout / deadlock – Philipp Mar 07 '16 at 09:48
6
If you are using IIS 7+, I would say leave the compression to IIS as it supports GZIP compression. Just turn it on.
On the other hand, compression is too close to the metal for the controller. Ideally controller should work in much higher level than bytes and streams.

Aliostad
- 80,612
- 21
- 160
- 208
-
1In general I agree, however IIS level compression would require configuration of any servers using it. – samus Sep 18 '17 at 14:22
3
Use a class and write the following code
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var acceptedEncoding = context.Response.RequestMessage.Headers.AcceptEncoding.First().Value;
if (!acceptedEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)
&& !acceptedEncoding.Equals("deflate", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
context.Response.Content = new CompressedContent(context.Response.Content, acceptedEncoding);
}
}
Now create another class and write the following code.
public class CompressedContent : HttpContent
{
private readonly string _encodingType;
private readonly HttpContent _originalContent;
public CompressedContent(HttpContent content, string encodingType = "gzip")
{
if (content == null)
{
throw new ArgumentNullException("content");
}
_originalContent = content;
_encodingType = encodingType.ToLowerInvariant();
foreach (var header in _originalContent.Headers)
{
Headers.TryAddWithoutValidation(header.Key, header.Value);
}
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;
switch (_encodingType)
{
case "gzip":
compressedStream = new GZipStream(stream, CompressionMode.Compress, true);
break;
case "deflate":
compressedStream = new DeflateStream(stream, CompressionMode.Compress, true);
break;
default:
compressedStream = stream;
break;
}
return _originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
{
if (compressedStream != null)
{
compressedStream.Dispose();
}
});
}
}
Now use the following attribute in Controller or in any api action method like this
[Route("GetData")]
[CompressFilter]
public HttpResponseMessage GetData()
{
}

samus
- 6,102
- 6
- 31
- 69

Debendra Dash
- 5,334
- 46
- 38
-
I have OWIN Middleware configured on my Web API and this is the only solution that worked for me. Plus, you can really target what you want to compress. Good solution! – Elferone May 05 '17 at 10:11
-
This will fail if you do "return (Ok());" in your controller method, because _originalContent will be null and you will get an exception about "The async operation did not return a System.Threading.Tasks.Task object."... how do I fix it? – CodeOrElse Dec 12 '18 at 10:09
-
Ah, just add this in OnActionExecuted(): "if (context.Response.Content == null) return;" – CodeOrElse Dec 12 '18 at 10:31
-
It will also fail (crash) if there is no Accept-Encoding header in the request. That First() method. Do this instead: string acceptedEncoding = string.Empty; var acceptedEncodingHeaders = context.Response.RequestMessage.Headers.AcceptEncoding; if (acceptedEncodingHeaders.Any()) acceptedEncoding = acceptedEncodingHeaders.First().Value; – CodeOrElse Dec 14 '18 at 09:51