DEV Community

Cover image for TIL: mime types and static assets in Umbraco
Sebastiaan Janssen
Sebastiaan Janssen

Posted on

TIL: mime types and static assets in Umbraco

A few months ago I wrote a post about learnings from the Umbraco Discord that week and then.. it went quiet 😅

With the best of intentions to keep that series going, it turns out there's just not enough hours in the day! I have ideas though to open up the information from Discord more but that will have to be bit more long term.

In the mean time, we did go down a fun exploration path with Dean Leigh yesterday thinking about adding custom MIME types to Umbraco and overriding existing ones if needed.

Screnshot of a Discord message where Dean is asking how to change MIME types

This turned into a thread where explored the actual needs and some solutions.

Program.cs vs Startup.cs

First off, I understand the confusion, Umbraco 9 was built on .net 5 and that required us to have Startup.cs and Program.cs.

In .net 6, Microsoft consolidated the two into Program.cs and updated all of their documentation to that effect. We're not ready yet in Umbraco to do the same, so there will be some confusion for a while.

So for now, in Umbraco you're encouraged never to touch Program.cs and only ever make changes to Startup.cs.

Mime types

As it turns out, Dean's original question was about trying to figure out how to host and serve the new(-ish) avif format, an image format with really great compression.

An image showing the difference in image quality between jpg and avif formats Image credit: Jake Archibald

Since .net doesn't yet support avif and neither does ImageSharp, files of this type just didn't have a MIME type available and trying to use them would result in a 404 error.

I remembered seeing an example recently to help cache static assets. These are specifically listing file types that ImageSharp doesn't touch. If you added something like jpg in that list, the request would be cached very early and never hit the ImageSharp middleware.

Adding/changing MIME types is pretty similar to adding cache control headers, so I played with that code to see how far we could get.

As it turns out, I was trying my code on webp files first, changing their MIME type from image/webp to image/text to see if it would work. It didn't! I was close though and with some great help from my colleague Ronald I understood that I was only changing the MIME type for .net and then the next thing kicked in: ImageSharp. When ImageSharp sees a request for a webp file, it reads the metadata from the file and sets the MIME type (correctly!) back to image/webp. So after a bit of experimenting we came up with the following code:

⚡⚠️ danger danger, this messes with the default webp content type, don't use in production⚡⚠️

using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.StaticFiles;
using SixLabors.ImageSharp.Web;
using SixLabors.ImageSharp.Web.Middleware;
using Umbraco.Cms.Core.Composing;

public class ContentTypeComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        var contentTypeProvider = new FileExtensionContentTypeProvider();
        contentTypeProvider.Mappings[".webp"] = "image/text";

        builder.Services.Configure<StaticFileOptions>(options => options.ContentTypeProvider = contentTypeProvider);
        builder.Services.Configure<ImageSharpMiddlewareOptions>(options =>
        {
            var prepareResponseAsync = options.OnPrepareResponseAsync;
            options.OnPrepareResponseAsync = async (context) =>
            {
                var formatUtilities = context.RequestServices.GetRequiredService<FormatUtilities>();
                if (formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out var extension) &&
                    contentTypeProvider.TryGetContentType('.' + extension, out var contentType))
                {
                    context.Response.ContentType = contentType;
                }

                await prepareResponseAsync(context);
            };
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Cool, that worked!

Screenshot of the Edge browser's network tab showing that the MIME type has updated

One caveat though, as you can see in the code above, this is updating the Mappings for an existing content type, and as we learned earlier, avif is not supported in .net yet and therefore there is no existing content type. We also don't have to think about ImageSharp since it doesn't support avif yet either.

So to make avif get a proper MIME type, the code needed to change a bit:

using Microsoft.AspNetCore.StaticFiles;
using Umbraco.Cms.Core.Composing;

public class ContentTypeComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        var contentTypeProvider = new FileExtensionContentTypeProvider();
        if (!contentTypeProvider.Mappings.ContainsKey(".avif"))
        {
            contentTypeProvider.Mappings.Add(".avif", "image/avif");
        }

        builder.Services.Configure<StaticFileOptions>(options => options.ContentTypeProvider = contentTypeProvider);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we added the MIME type if the mapping doesn't yet exist, if it ever starts to exist in the future we assume it gets the correct mapping anyway.

Bonus

If you want to learn more about headers and CORS, my colleague Warren has a great Hack Make Do video on this topic as well!

Conclusion

And there we have it, now we can add a MIME type to any unknown file format and we learned at the same time how to override MIME types for ImageSharp. Plus.. now that we know how to hook into the ImageSharp middleware options, we could probably think of more fun things to do in there as well.

Top comments (0)