Posting compressed JSON Content to ASP.NET Web API Controller

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.

This entry was posted in Technical and tagged , . Bookmark the permalink.