I needed to do this on a project last year. I meant to share my solution but I forget.

Here is the code if you are looking for it.

using System;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.SessionState;
using CodeShare.Core.Extensions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;

namespace CodeShare.Core.Handlers
{
    public class HtmlHandler : IHttpHandler, IRequiresSessionState
    {
        public void ProcessRequest(HttpContext context)
        {
            var contextBase = context.ToBase();
            var originalUrl = context.Request.Url.PathAndQuery;

            if (originalUrl.StartsWith("/umbraco"))
            {
                UseExistingStaticFileHandler(context);
                return;
            }

            string path = context.Server.MapPath(context.Request.RawUrl);

            // Is there a file that already exists on the file system?
            // If so, always serve that.
            if (System.IO.File.Exists(path))
            {
                Umbraco.Web.Composing.Current.Logger.Debug<HtmlHandler>("Streaming specified static file from disk.");

                contextBase.Response.TransmitFileContent(path);
                return;
            }

            // Ensure there is an Umbraco context that is necessary for data access.
            var umbracoContextFactory = Umbraco.Web.Composing.Current.Factory.GetInstance(typeof(IUmbracoContextFactory)) as IUmbracoContextFactory;
            var umbracoContext = umbracoContextFactory?.EnsureUmbracoContext(contextBase)?.UmbracoContext;
            if (umbracoContext == null)
            {
                Umbraco.Web.Composing.Current.Logger.Warn<HtmlHandler>("Umbraco context is null even after ensuring there is one!");
                throw new HttpException((int)HttpStatusCode.NotFound, "Page Not Found");
            }


            //use the correct content type alias here for your root node
            var homeNode = umbracoContext.Content.GetAtRoot().FirstOrDefault(x => x.ContentType.Alias == "homePage");

            IPublishedContent content = null;
            if (homeNode != null)
            {
                content = GetContentWithMatchingLegacyUrl(homeNode, path: originalUrl);
            }

            if (content != null)
            {
                context.Response.RedirectPermanent(content.Url);
            }
            else
            {
                UseExistingStaticFileHandler(context);
            }

        }

        private static void UseExistingStaticFileHandler(HttpContext context)
        {
            Type type = typeof(HttpApplication).Assembly.GetType("System.Web.StaticFileHandler", true);
            IHttpHandler handler = (IHttpHandler)Activator.CreateInstance(type, true);
            handler.ProcessRequest(context);
        }

        public bool IsReusable => false;

        private static IPublishedContent GetContentWithMatchingLegacyUrl(IPublishedContent rootNode, string path)
        {
            return rootNode.DescendantsOrSelf().FirstOrDefault(
                x => x.HasValue("legacyUrl") && x.Value<string>("legacyUrl") == path);
        }
    }
}

I hope it helps you, and more importantly me when I need the code in 6 months time

Paul Seal

Umbraco MVP and .NET Web Developer from Derby (UK) who specialises in building Content Management System (CMS) websites using MVC with Umbraco as a framework. Paul is passionate about web development and programming as a whole. Apart from when he's with his wife and son, if he's not writing code, he's thinking about it or listening to a podcast about it.

Proudly sponsored by

Moriyama

  • Moriyama build, support and deploy Umbraco, Azure and ASP.NET websites and applications.
AppVeyor

  • CI/CD service for Windows, Linux and macOS
  • Build, test, deploy your apps faster, on any platform.
elmah.io

  • elmah.io is the easy error logging and uptime monitoring service for .NET.
  • Take back control of your errors with support for all .NET web and logging frameworks.
uSync Complete

  • uSync.Complete gives you all the uSync packages, allowing you to completely control how your Umbraco settings, content and media is stored, transferred and managed across all your Umbraco Installations.
uSkinned

  • More than a theme for Umbraco CMS, take full control of your content and design with a feature-rich, award-nominated & content editor focused website platform.
UmbHost

  • Affordable, Geo-Redundant, Umbraco hosting which gives back to the community by sponsoring an Umbraco Open Source Developer with each hosting package sold.