Go to main content

Localizing 404 page in Kentico Xperience

In this post I will provide you with code that will localize your 404 page in your Kentico Xperience website.

The problem

In my Xperience website, I am using 2 cultures. When I was about to create a 404 page, I didn't want to re-invent the wheel. Therefore, I took out the 404 page implementation from the Dancing Goat site which is a sample site that comes with Kentico Xperience in one package.

Regrettably, I found out that the implementation is not ready for localization and must be adjusted to show localized strings. With the kind help of Kentico's support, I was able to find a solution.

Please note, that this solution is valid for URL structure where 

  • URL paths for the default culture do not contain culture code (i.e. "/clanky" for "cs-CZ" default culture)
  • URL paths for other cultures contain culture code (i.e. "/en-us/articles" for "en-US" culture)

The solution

There are a couple of code files that you have to create.

HttpErrorsController.cs

using Microsoft.AspNetCore.Mvc;

namespace DancingGoat.Controllers
{
    public class HttpErrorsController : Controller
    {
        public IActionResult Error(int code)
        {
            if (code == 404)
            {
                return View("NotFound");
            }

            return StatusCode(code);
        }
    }
}

NotFoundSiteCultureConstraint.cs

using Kentico.Content.Web.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using System;
using System.Globalization;

namespace DancingGoat
{
    public class NotFoundSiteCultureConstraint : IRouteConstraint
    {
        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            var cultureCode = values[routeKey]?.ToString();

            if (string.IsNullOrEmpty(cultureCode))
            {
                if (values != null && values.Count == 3 && string.Equals("Error", values["action"].ToString(), StringComparison.OrdinalIgnoreCase) && string.Equals("HttpErrors", values["controller"].ToString(), StringComparison.OrdinalIgnoreCase))
                {
                    var originalTarget = httpContext.Features.Get<IHttpRequestFeature>().RawTarget;
                    cultureCode = originalTarget.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[0];
                }

                if (!string.IsNullOrEmpty(cultureCode))
                {
                    try
                    {
                        var cultureInfo = CultureInfo.GetCultureInfo(cultureCode);
                    }
                    catch (CultureNotFoundException)
                    {
                        cultureCode = null;
                    }
                }

                values[routeKey] = cultureCode;
            }

            return new SiteCultureConstraint().Match(httpContext, route, routeKey, values, routeDirection);
        }
    }
}

NotFound.cshtml - This is a view and it should contain your own markup that fits your website. Here I post an example that uses Localization strings. They could be defined in the Xperience Admin in the Localization application.

@{
    ViewBag.Title = @ResHelper.GetString("notfoundtitle");
}

<main id="main">
    <section class="error">
        <div class="error__heading">
            <div class="error__text">@ResHelper.GetString("notfoundheading")</div>
        </div>
    </section>
</main>

In the Startup.cs file, add the following code in the Configure method after endpoints.Kentico().MapRoutes();

endpoints.MapControllerRoute(
    name: "error",
    pattern: "/error/{code}",
    defaults: new { controller = "HttpErrors", action = "Error" },
    constraints: new { culture = new NotFoundSiteCultureConstraint() }
);

And that's it.

Side notes

Considering the Dancing Goat site, the only extra piece in the implementation is the NotFoundSiteCultureConstraint.cs file. What I really like here is the way the originalTarget is obtained. You can also use that in your controller to add some extra logic to your 404 page.

Example - Redirects article details that are not localized to a listing page.

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using System;

namespace jobs.kentico.com.Web.Controllers
{
    public class HttpErrorsController : Controller
    {
        public IActionResult Error(int code, string culture)
        {
            var originalTarget = Request.HttpContext.Features.Get<IHttpRequestFeature>().RawTarget;
            var path = originalTarget.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            var pathSegment = "";

            if (path.Length > 1 && culture == "en-us")
            {
                pathSegment = path[1];
            } 
            else if (path.Length > 0 && culture == "cs-CZ")
            {
                pathSegment = path[0];
            }

            if (code == 404) 
            {
                if (culture == "en-us" && pathSegment == "articles")
                {
                    return LocalRedirect("/en-us/articles");
                } 
                else if (culture == "cs-CZ" && pathSegment == "clanky")
                {
                    return LocalRedirect("/clanky");
                }
                else
                {
                    return View("NotFound");
                }
            }

            return StatusCode(code);
        }
    }
}


Further reading

all posts