I had a requirement to POST gzipped JSON content to a .NET web api controller. It turns out the technique for doing this is not easy, or at least not common knowledge. This is the solution I came up with that works:
public class GZipToJsonHandler : DelegatingHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Handle only if content type is 'application/gzip' if (request.Content.Headers.ContentType == null || request.Content.Headers.ContentType.MediaType != "application/gzip") { return base.SendAsync(request, cancellationToken); } // Read in the input stream, then decompress in to the outputstream. // Doing this asynronously, but not really required at this point // since we end up waiting on it right after this. Stream outputStream = new MemoryStream(); Task task = request.Content.ReadAsStreamAsync().ContinueWith(t => { Stream inputStream = t.Result; var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); gzipStream.CopyTo(outputStream); gzipStream.Dispose(); outputStream.Seek(0, SeekOrigin.Begin); }); // Wait for inputstream and decompression to complete. Would be nice // to not block here and work async when ready instead, but I couldn't // figure out how to do it in context of a DelegatingHandler. task.Wait(); // This next section is the key... // Save the original content HttpContent origContent = request.Content; // Replace request content with the newly decompressed stream request.Content = new StreamContent(outputStream); // Copy all headers from original content in to new one foreach (var header in origContent.Headers) { request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } // Replace the original content-type with content type // of decompressed data. In our case, we can assume application/json. A // more generic and reuseable handler would need some other // way to differentiate the decompressed content type. request.Content.Headers.Remove("Content-Type"); request.Content.Headers.Add("Content-Type", "application/json"); return base.SendAsync(request, cancellationToken); } }
Added to the MessageHandlers in WebApiConfig.cs with:
config.MessageHandlers.Add(new GZipToJsonHandler());
Using this approach, existing controllers which normally work with JSON content and automatic model binding, continue to work without any changes. Related StackOverflow question with my answer here.