Friendly Error Pages in Umbraco 9

In this article, we will look at how to set up friendly error pages in Umbraco 9.

Friendly Error Pages in Umbraco 9

Page Not Found - 404 Error Page

In Version 8 and previous versions of Umbraco, the simplest way to set up a 404 page was to add it to the umbracoSettings.config file as shown here. When it comes to Umbraco 9, this config file no longer exists. As a matter of fact, all the XML-based config files have been replaced by JSON-based appsettings.json file, embracing the ASP.NET Core configuration. To set up 404 error pages in Umbraco, you can make use of the Error404Collection section in appsettings.json. This is added to the Content subsection as shown below. I am using the ContentKey here but ContentId or ContentXPath can also be used instead of the key.

"Error404Collection": [
 {
   "Culture": "default",
   "ContentKey": "abd58ca0-c700-47f3-b7ab-6672583572ce"
 }
]

If you have language variants and wish to have a 404 page per language culture, you can specify the page per culture as shown below. 

"Error404Collection": [
  {
    "Culture": "default",
    "ContentKey": "abd58ca0-c700-47f3-b7ab-6672583572ce"
  },
  {
    "Culture": "en-gb",
    "ContentKey": "b35174d1-2be3-47df-b42d-e00a7ee27e43"
  }
]

If you have language variants and a 404 page per culture is not specified, it falls back to the 404 page marked for the default culture. 

You can also approach 404 pages programmatically, which can be helpful in many cases. This is done by customising the inbound pipeline in Umbraco using an IContentFinder, more specifically IContentLastChanceFinder. The IContentLastChanceFinder always returns a 404 status code. 

To achieve this, we first need to create a new implementation of the IContentLastChanceFinder and return a boolean based on whether the custom 404 page was found or not. 

public class PageNotFoundContentFinder : IContentLastChanceFinder
    {
        private readonly IDomainService _domainService;
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;

        public PageNotFoundContentFinder(IDomainService domainService, IUmbracoContextAccessor umbracoContextAccessor)
        {
            _domainService = domainService;
            _umbracoContextAccessor = umbracoContextAccessor;
        }

        public bool TryFindContent(IPublishedRequestBuilder request)
        {
            var allDomains = _domainService.GetAll(true).ToList();
            var domain = allDomains?
                .FirstOrDefault(f => f.DomainName == request.Uri.Authority
                || f.DomainName == $"https://{request.Uri.Authority}"
                || f.DomainName == $"http://{request.Uri.Authority}");

            var siteId = domain != null ? domain.RootContentId : allDomains.Any() ? allDomains.FirstOrDefault()?.RootContentId : null;

            if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext))
            {
                return false;
            }
            var siteRoot = umbracoContext.Content.GetById(false, siteId ?? -1);

            if (siteRoot is null)
            {
                return false;
            }

            var notFoundNode = siteRoot.Children.FirstOrDefault(f => f.ContentType.Alias == Error.ModelTypeAlias);

            if (notFoundNode is not null)
            {
                request.SetPublishedContent(notFoundNode);
            }

            return request.PublishedContent is not null;
        }
    }

This implementation then needs to be set as the ContentLastChanceFinder using Startup.

SetContentLastChanceFinder<PageNotFoundContentFinder>()

This feature has been well documented in Umbraco Docs. The programmatic way of setting 404 pages by customising the inbound pipeline is not a new Umbraco 9 feature, it has been there in older versions of Umbraco as well. 

From what I understand, the IContentLastChanceFinder implementation takes priority over the Error404Collection.

There is also another option, the Hot Chilli Page Not Found Manager package created by the fantastic Nik Rimington. You can install the package from Nuget and start using it straight away! 

500 Error Pages

500 error pages, in my opinion, is a whole new story with .NET. It is still possible to have a web.config with customErrors section but then this ties up your application to use a Windows-based hosting environment. 

ASP.NET Core provides an exception handling middleware for custom error handling. This can be used in the Startup by calling the middleware and passing a error handling path which can be a static html file for your 500 errors, url to an error page in Umbraco or even a controller action. In my case, I am setting it to be a static file. 

app.UseExceptionHandler("/error.html");

This static file must be located in the wwwroot folder.

There is lots more to this feature that you can read about in the Microsoft Official Docs.

Umbraco 9 also covers us for any issues during boot up. This was a feature introduced in Umbraco 8 and is available in Umbraco 9 too. The default, pretty, boot page is available at ~/umbraco/views/errors/BootFailed.html. However this can be customised by creating a new HTML file with the name BootFailed.html. This file must be in the folder config/errors in wwwroot