Aaron Sadler
Posted by:

Aaron Sadler

Category

Umbraco

We wanted to bring back WebP to our website now that we are running on Umbraco V10, we had this back when the site was running on Umbraco V8 (We followed this post for Umbraco V8).

Due to Umbraco 10 being released just over a week ago, this doesn't seem to be something which has been hacked about with too much yet.

The first place we looked for some hints, was on the Umbraco forum.

We came across a comment on a topic by the user Kamil containing some great code showing how to do this using Middleware.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;

public class WebPMiddleware
{
    private readonly RequestDelegate _next;

    public WebPMiddleware(RequestDelegate next)
    {
        _next = next;

    }

    public async Task Invoke(HttpContext httpContext)
    {
        if (IsImageRequest(httpContext) &&
            httpContext.Request.GetTypedHeaders()?.Accept != null 
            && httpContext.Request.GetTypedHeaders().Accept.Any(aValue => aValue.MediaType.Value == "image/webp"))
        {
            var updatedQueryString = GetUpdatedQueryString(httpContext);

            var qb1 = new QueryBuilder(updatedQueryString);

            httpContext.Request.QueryString = qb1.ToQueryString();
        }

        await _next(httpContext);
    }

    private bool IsImageRequest(HttpContext httpContext)
    {
        var path = httpContext.Request.Path.ToString();

        return path.EndsWith("png") || path.EndsWith("jpg") || path.EndsWith("jpeg");
    }

    private List<KeyValuePair<string, string>> GetUpdatedQueryString(HttpContext httpContext)
    {
        var queryitems = httpContext.Request.Query.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToList();
        var queryParameters = new List<KeyValuePair<string, string>>();

        foreach (var item in queryitems)
        {
            var value = item.Value;

            if (item.Key == "format")
            {
                value = "webp";
            }

            var newQueryParameter = new KeyValuePair<string, string>(item.Key, value);

            queryParameters.Add(newQueryParameter);
        }

        if (!queryParameters.Any())
        {
            queryParameters.Add(new KeyValuePair<string, string>("format", "webp"));
        }

        return queryParameters;
    }
}
public static class WebPMiddlewareExtensions
{
    public static IApplicationBuilder UseWebP(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<WebPMiddleware>();
    }
}

And then in the Configure part of Startup.cs

app.UseWebP();

At first this looked great, however this would only work with images not running through the cropper.

Our main use case for dynamic WebP images over using the picture html tag was for use with the Rich Text editor, so in addition to the above code we added the following to the ConfigureServices part of startup.cs

services.AddImageSharp(options =>
    {
        options.OnParseCommandsAsync = c =>
        {
            if (c.Context != null)
            {
                if (c.Context.Request.GetTypedHeaders().Accept
                        .Any(aValue => aValue.MediaType.Value == "image/webp"))
                {
                    var path = c.Context.Request.Path.ToString();

                    if (!c.Commands.Contains("webp") || !c.Commands.Contains("noformat") && path.EndsWith("png") || path.EndsWith("jpg") || path.EndsWith("jpeg"))
                    {
                        c.Commands.Remove("format");
                        c.Commands.Add("format", "webp");
                        c.Context.Response.Headers.Add("Vary", "Accept");
                    }
                }
            }
            bool doesntWantFormat = c.Commands.TryGetValue("noformat", out string value);

            if (doesntWantFormat)
            {
                c.Commands.Remove("format");
            }
            return Task.CompletedTask;
        };
    });
}

What we are doing in the code above is first testing the browser supports webp, next we are checking to see if we already have the format set to webp and not noformat (this is explained below), if so then we skip over the rest.

Next we are checking it's a format we want to convert (png, jpg or jpeg).

Finally if that is all ok we will replace the format query string value with webp, this will force Umbraco to return the image in image/webp format.

If we want to disable this on a particular image, we can add the query string key of format and a value of noformat to disable the conversion.

If there is a nicer way to do this, then please let us know in the comments below!

Updated to add the Vary header to the response, thanks Ronald!

Comments
Tony Southworth

Posted: 12th July 2023

Hi Aaron, Thanks for this example, I\'ve been using it for a while. I have just come across an issue where GIFs were being converted by it which was breaking the animation. To fix it I have altered the if statement in the ConfigureServices function to this: if (!c.Commands.Contains("webp") && !c.Commands.Contains("noformat") && (path.EndsWith("png") || path.EndsWith("jpg") || path.EndsWith("jpeg"))) That now correctly targets png and jpg files. Thanks Tony

Arjan

Posted: 19th July 2022

Great article! For responsive image sizes I\'ve previously been using the <picture> element with <source type="image\/webp" \/> and an additional <source \/> without a type-attribute as a fallback for browsers that don\'t support WEBP. I really like that your solution checks for WEBP support server-side which saves me a lot of duplicate <source \/> elements in the views. I\'m definitely going to try it out!

Post a comment

Fields marked with an * (asterisk) are required


Recent Posts

Tips & Tricks
How to use Cloudflare Workers and Transform Rul...

This post explains how to m...

News
UmbHost Limited is now a Silver Umbraco Partner

We are now officially a Sil...

Umbraco
How to pass a Content Security Nonce (CSP) to G...

How to use a CSP nonce with...

News
UmbCheckout 1.0.0 & UmbCheckout.StarterKit.Stri...

The stable version of UmbCh...

News
Voting is now open for the Green Business of th...

We've been shortlisted! - P...

ADVERTISTING
Browse Umbraco Hosting
Umbraco Hosting Starting At £10.00/month