Working with Save-Data header in ASP.NET Core

February 03, 2019 by Anuraj

ASP.NET Core

This post is about working with Save-Data header in ASP.NET Core. The Save-Data client hint request header available in Chrome, Opera, and Yandex browsers lets developers deliver lighter, faster applications to users who opt-in to data saving mode in their browser.

One fairly straightforward technique is to let the browser help, using the Save-Data request header. By identifying this header, a web page can customize and deliver an optimized user experience to cost- and performance-constrained users.

For this post, a middleware is getting implemented, which is looking for Image files(JPG), and it save-data header exists, it is compressing the images and delivering it.

public class SaveDataMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IHostingEnvironment _hostingEnvironment;
    private static readonly string[] suffixes = new string[] {
        ".jpg",
        ".jpeg"
    };

    public SaveDataMiddleware(RequestDelegate next, IHostingEnvironment hostingEnvironment)
    {
        _next = next;
        _hostingEnvironment = hostingEnvironment;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var path = httpContext.Request.Path;
        if (!IsImagePath(path))
        {
            await _next.Invoke(httpContext);
            return;
        }

        var isSaveDataEnabled = false;
        if (httpContext.Request.Headers.TryGetValue("save-data", out StringValues saveDataHeaders))
        {
            isSaveDataEnabled = saveDataHeaders.Count == 1 && 
                saveDataHeaders[0].Equals("on", StringComparison.OrdinalIgnoreCase);
        }

        if (isSaveDataEnabled)
        {
            var imagePath = Path.Combine(_hostingEnvironment.WebRootPath, 
                path.Value.Replace('/', Path.DirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar));
            using (var image = Image.FromFile(imagePath))
            {
                using (var lowQualityImage = new Bitmap(image.Width, image.Height))
                {
                    using (var graphics = Graphics.FromImage(lowQualityImage))
                    {
                        graphics.InterpolationMode = InterpolationMode.Low;
                        graphics.SmoothingMode = SmoothingMode.HighSpeed;
                        graphics.CompositingQuality = CompositingQuality.HighSpeed;
                        graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
                        var imageRectangle = new Rectangle(0, 0, image.Width, image.Height);
                        graphics.DrawImage(image, imageRectangle);
                        using (var memoryStream = new MemoryStream())
                        {
                            lowQualityImage.Save(memoryStream, image.RawFormat);
                            httpContext.Response.ContentLength = memoryStream.Length;
                            await httpContext.Response.Body.WriteAsync(memoryStream.ToArray(), 0, (int)memoryStream.Length);
                        }
                    }
                }

            }
        }
        else
        {
            await _next(httpContext);
        }
    }
    private bool IsImagePath(PathString path)
    {
        if (path == null || !path.HasValue)
        {
            return false;
        }

        return suffixes.Any(x => x == Path.GetExtension(path.Value));
    }
}

And next you can configure extension method to add it to http pipeline.

public static class SaveDataMiddlewareExtensions
{
    public static IApplicationBuilder UseSaveDataMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<SaveDataMiddleware>();
    }
}

You need to add this middleware before StaticFilesMiddleware, otherwise it won’t work properly.

And you can verify this implementation using Data Saver chrome extension.

Chrome Data-Saver extension

If Data-Saver mode is enabled, browser will send a HTTP Request header.

Data-Saver header

The page display 5 images. And here is the network tab, without save-data mode.

Chrome network tab - without save-data header

And here is Network tab, after enabling the save-data header.

Chrome network tab - with save-data header

In this post, I am only handling JPG images, you can also exclude stylesheets and fonts etc, if it is not critical for the page. You can get more details about save-data header and various implementations here - Delivering Fast and Light Applications with Save-Data

Happy Programming :)

Support My Work

If you find my content helpful, consider supporting my work. Your support helps me continue creating valuable resources for the community.

Share this article

Found this useful? Share it with your network!

Copyright © 2025 Anuraj. Blog content licensed under the Creative Commons CC BY 2.5 | Unless otherwise stated or granted, code samples licensed under the MIT license. This is a personal blog. The opinions expressed here represent my own and not those of my employer. Powered by Jekyll. Hosted with ❤ by GitHub