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.
  • El Tori

    Useful!!! Thanks 😀

  • Hannaan

    My model in my api controller is null after the decompression

    • Hannaan

      Any help/suggestions ?

    • kaliatech

      Are you able to post without the compression? I would make sure that works first. Did you add the GZipToJsonHandler message handler correctly? (I edited my post to include that line in WebApiConfig.cs). If still having issues, consider posting a question to stackoverflow. However, you will need to provide more information regarding your setup.

  • Dan

    Hi

    What code did you use for sending the compressed content from the client?

    Is it possible to have HttpClient send compressed content from the client?
    I already have a HttpClient that attaches a client certificate for authentication but do not know how to change the code to have compression
    Thanks

  • Dan

    Thank you for your help and fast response.
    I will give it a try