DEV Community

Cover image for Simple redirect engine from the Umbraco content tree
Søren Kottal
Søren Kottal

Posted on

Simple redirect engine from the Umbraco content tree

Yes, yes, I know... Skybrud.Redirects has everything you need and looks fantastic.

But sometimes you need redirects to be part of your site navigation, and often you generate your navigation based on the hierarchy in the Content tree.

For this a simple Redirect document type is handy. So let's build one of those.

We want to let the editor select where to redirect the user, so I add a Multi Url Picker set to minimum 1 and maximum 1 link. Or in other words 1 link required, nothing more, nothing less.

Image description

The sweet thing about the Multi Url Picker is that it let's the editor pick between content, media and external (or internal) urls, and the interface is intuitive and easy to work with.

To actually create the redirect, the simplest option is to add a template to the document type and in there add the following code:

@using Umbraco.Cms.Web.Common.PublishedModels;
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.RedirectPage>
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@{
    Layout = null;
    Context.Response.Redirect(Model.Redirect.Url, permanent: true);
}
Enter fullscreen mode Exit fullscreen mode

This will, when the page renders, redirect the user to the selected link, using a permanent (301) redirect.

Good enough for starts, but we can make it even better.

When using a 301 redirect, "link juice" will not flow as much as when using a real link. SEO experts can speak for days about this. And to me its not very nice to lure the user onto an internal url, just to redirect them away to some other site.

So why not make Umbraco use the linked url as the url for the node?

We can do that using the IUrlProvider interface. Here's an example of how you can change the url of the redirect nodes from being generated based on the content tree, to actually be the end destinations url.

using Umbraco.Cms.Core.Routing;

namespace MyProject.UrlProviders;

public class RedirectPageUrlProvider : IUrlProvider
{
    public IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
    {
        return Enumerable.Empty<UrlInfo>();
    }

    public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current)
    {
        if (content is not ContentModels.RedirectPage redirectPage)
            return null;

        return new UrlInfo(redirectPage.Redirect?.Url ?? "#", true, culture);
    }
}
Enter fullscreen mode Exit fullscreen mode

In here we implement the GetUrl method from the interface, checking if the content node in question is our Redirect Page, and then returning the url selected in the Url Picker in the node.

To register this UrlProvider in Umbraco, you can either implement an IUserComposer like this:

public class RegisterCustomUrlProviderComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.UrlProviders().Insert<RedirectPageUrlProvider>();
    }
}
Enter fullscreen mode Exit fullscreen mode

This will automatically get picked up when Umbraco starts, and register your new UrlProvider.

When generating the url for a node, Umbraco runs through all registered UrlProviders for each node it needs to generate urls for, so we start by short circuiting this provider by returning null if the actual node is not our redirect content type.

If it is our redirect type, we fetch the url from our multi url picker and return that one. And voila, in stead of /path/to/redirect, the url is now (url).

So we actually don't need our template anymore.

But I like to wear both belt and braces (as we say in Denmark), so I actually want to keep it. But instead of having the logic in a template, I move it to a RenderController instead.

public class RedirectPageController : RenderController
{
    public RedirectPageController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor)
    {
    }

    public override IActionResult Index()
    {
        var redirectPage = CurrentPage as ContentModels.RedirectPage;
        if (redirectPage.Redirect.Url.IsNullOrWhiteSpace()) return NotFound();

        return RedirectPermanent(redirectPage.Redirect.Url);
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the logic is pretty much the same. I get the url, I redirect to the url.

If no url is selected I return 404.

I don't think this should be a replacement for a proper redirects solution, but it's a great fit when you need external links and other redirects in your sitemap.

Top comments (0)