404 Error handling in JSS layout service

22 November 2019 at 00:00 by Menderes Demir - Post a comment

To have a correct 404 error handling with Sitecore in combination with JSS, so that when the layout service is called, it validates if the item and the language version exist. If the item doesn’t exist, the user will get a 404 statuscode and gets the 404 Sitecore item content served. I started according to the following blogpost (http://blog.martinmiles.net/post/handling-404-in-helix), so credits for Martin Miles. Unfortunately, the validation didn’t match for the layout service, because the Context.Item isn’t available. I had to resolve an item by the query parameter “item”.

Follow the next steps to return a 404 Sitecore item with the correct StatusCode in the layout service.

Create a config file: My.Project.Common.RequestErrors.config:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <httpRequestBegin>
                <processor type="My.Project.Common.Pipelines.HandleRequestError, My.Project.Common"
                           patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
            </httpRequestBegin>
        </pipelines>
    </sitecore>
</configuration>

 Create a processor: HandleRequestError.cs:

public class HandleRequestError : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {

  // The requested item is checked if it exists.
        if (IsValidItem(args))
        {
            return;
        }

  // Prevent (Sitecore) pages on file are skipped.
        if (File.Exists(HttpContext.Current.Server.MapPath(args.Url.FilePath)))
        {
            return;
        }

  // Serve editable 404 page item.
        if (Context.Database != null)
        {
            Context.Item = Context.Database.GetItem(Context.Site.StartPath + "/404");
        }
     // Set the StatusCode to NotFound - 404
        HttpContext.Current.Response.TrySkipIisCustomErrors = true;
        HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.NotFound;
    }

    private static bool IsValidItem(HttpRequestArgs args)
    {
        var uri = new Uri(args.RequestUrl.AbsoluteUri);

// Because there’s no context item available in the layout service, the item path is retrieved from the querystring “item”
        var query = HttpUtility.ParseQueryString(uri.Query).Get("item") ?? "";
        var queryParam = query.Replace("-", " ");
    
  // Check if the item can be retrieved in any language while using the SecurityDisabler. This way protected pages are checked also.
        using (new SecurityDisabler())
        {
            var resolveItem = Context.Database?.GetItem(Context.Site.StartPath + queryParam);
            if (resolveItem != null)
            {
                bool checkLanguageVersion = HasLanguageVersion(resolveItem, Context.Language.Name);
                return checkLanguageVersion;
            }
        }

        return false;
    }

    private static bool HasLanguageVersion(Item item, string languageName)
    {
        var language = item.Languages.FirstOrDefault(l => l.Name == languageName);
        if (language != null)
        {
            var languageSpecificItem = Context.Database.GetItem(item.ID, language);
            if (languageSpecificItem != null && languageSpecificItem.Versions.Count > 0)
            {
                return true;
            }
        }
        return false;
    }
}

Latest