Compress ASP.NET Core Web API/REST API HTTPS Output

This example uses ASP.NET Core 3.1. It may be compatible with later versions of .NET including .NET 5 and .NET 6. See how to enable Web API HTTPS compression for use with Blazor WASM.

Compressing Web API (REST API) output makes an application so much more responsive (there is a security issue called BREACH). Enable site-wide ASP.NET HTTPS compression.

This article shows how to enable more than one compression algorithm for the Web API. The rest of the site remains uncompressed.

The key to making this work is converting all objects to a JSON string and then compressing it with the BrotliStream first then the GZipStream second and sending the compressed data to the client as a File.

Here is the snipped of code that does all of this work.

var json = System.Text.Json.JsonSerializer.Serialize(data); // CONVERT DATA TO JSON STRING
if (!string.IsNullOrEmpty(Request.Headers["Accept-Encoding"])) // CHECK THAT REQUEST SUPPORTS COMPRESSION
{
	var encodings = Request.Headers["Accept-Encoding"].ToString().Split(',');
	for(int i = 0; i < encodings.Length; i++)
		encodings[i] = encodings[i].Trim();
	if (Array.IndexOf(encodings, "br") > -1)
	{
		Response.Headers.Append("Content-Encoding", "br");
		var compressedBytes = await Compressor.BrotliCompressBytesAsync(System.Text.Encoding.UTF8.GetBytes(json), cancel);
		return File(compressedBytes, "application/json"); // RETURN COMPRESSED DATA AS A FILE
	}
	if (Array.IndexOf(encodings, "gzip") > -1)
	{
		Response.Headers.Append("Content-Encoding", "gzip");
		var compressedBytes = await Compressor.GZipCompressBytesAsync(System.Text.Encoding.UTF8.GetBytes(json), cancel);
		return File(compressedBytes, "application/json"); // RETURN COMPRESSED DATA AS A FILE
	}
}
Response.ContentType = "application/json"; // ADD THE CONTENT TYPE
return Content(json); // RETURN NON-COMPRESSED DATA

USE THIS UTILITY TO TEST THE HTTP COMPRESSION.

Here is the WeatherForecast controller's code which should return compressed data over an HTTPS connection for a JavaScript client (using either window.fetch() or XMLHttpRequest()).

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;

namespace ProjectName.Server.Controllers
{
	[ApiController]
	[Route("[controller]")]
	public class WeatherForecastController : ControllerBase
	{
		private static readonly string[] Summaries = new[]
		{
			"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
		};

		private readonly ILogger<WeatherForecastController> logger;

		public WeatherForecastController(ILogger<WeatherForecastController> logger)
		{
			this.logger = logger;
		}

		[HttpGet]
		public async Task<IActionResult> Get(System.Threading.CancellationToken cancel) // NOTE: RETURN TYPE IS IActionResult
		{
			var rng = new Random();
			var data = Enumerable.Range(1, 2000).Select(index => new WeatherForecast // NOTE: NOTICE THE SIZE OF THE ARRAY
			{
				Date = DateTime.Now.AddDays(index),
				TemperatureC = rng.Next(-20, 55),
				Summary = Summaries[rng.Next(Summaries.Length)]
			});
			var json = System.Text.Json.JsonSerializer.Serialize(data);
			if (!string.IsNullOrEmpty(Request.Headers["Accept-Encoding"]))
			{
				var encodings = Request.Headers["Accept-Encoding"].ToString().Split(',');
				for(int i = 0; i < encodings.Length; i++)
					encodings[i] = encodings[i].Trim();
				if (Array.IndexOf(encodings, "br") > -1)
				{
					Response.Headers.Append("Content-Encoding", "br");
					var compressedBytes = await Compressor.BrotliCompressBytesAsync(System.Text.Encoding.UTF8.GetBytes(json), cancel);
					return File(compressedBytes, "application/json");
				}
				if (Array.IndexOf(encodings, "gzip") > -1)
				{
					Response.Headers.Append("Content-Encoding", "gzip");
					var compressedBytes = await Compressor.GZipCompressBytesAsync(System.Text.Encoding.UTF8.GetBytes(json), cancel);
					return File(compressedBytes, "application/json");
				}
			}
			Response.ContentType = "application/json"; // ADD THE CONTENT TYPE
			return Content(json); // return non-compressed data
		}
	}

	internal class Compressor
	{
		public static async Task<byte[]> BrotliCompressBytesAsync(byte[] bytes, System.Threading.CancellationToken cancel)
		{
			using (var outputStream = new MemoryStream())
			{
				using (var compressionStream = new BrotliStream(outputStream, CompressionLevel.Optimal))
				{
					await compressionStream.WriteAsync(bytes, 0, bytes.Length, cancel);
				}
				return outputStream.ToArray();
			}
		}
		public static async Task<byte[]> GZipCompressBytesAsync(byte[] bytes, System.Threading.CancellationToken cancel)
		{
			using (var outputStream = new MemoryStream())
			{
				using (var compressionStream = new GZipStream(outputStream, CompressionLevel.Optimal))
				{
					await compressionStream.WriteAsync(bytes, 0, bytes.Length, cancel);
				}
				return outputStream.ToArray();
			}
		}
	}
}

Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site.
OK