Using other GraphQL types

Recently I needed to expose a GraphQL query that accepted a list of Sitecore item IDs and returned the item data for those IDs. I wanted to use the Content Search API for efficiency reasons and because the items might not all be colocated in a folder.

Out of the box, the Sitecore GraphQL Search query does not allow you to do this, but a look into the GraphQL assembly revealed that the ListGraphType might do the job.

The resulting extensions to Sitecore.Services.GraphQL.Content.Queries.SearchQuery looked like this:

public class MySearchQuery : Sitecore.Services.GraphQL.Content.Queries.SearchQuery
    {
        public MySearchQuery()
        {
            Arguments.Add(new QueryArgument<ListGraphType<StringGraphType>>()
            {
                Name = "itemIds",
                Description = "Filter by Sitecore item IDs"
            });
        }

        //override the ContentSearchResults method here...
}

The GraphQL query was then able to pass in an array of item IDs which looked something like this:

query ExtendedSearch {
	searchItemsById(itemIds:["6a6f0d75-2a1d-4f70-b639-622d308d9e11","{EF1848D8-6C43-4E52-A04F-287A12E19D12}"]) {
      results {
      items {
        item {
          id
          field(name:"myItemField"){
            value
          }
        }
      }
    }
  }
}

One thing to note is that, although you are passing in a Guid like “6a6f0d75-2a1d-4f70-b639-622d308d9e11”, the Sitecore GraphQL content search results will come back with a value like “6A6F0D752A1D4F70B639622D308D9E11”.

GraphQL and Sitecore – video series Part 1

Today I released Part 1 of a video series on using GraphQL with Sitecore. In this first part, I cover getting up and running with GraphQL through:

  • installing Sitecore’s JavaScript Services Server components
  • setting up an endpoint configuration
  • creating and using an API key, and
  • running a query in the UI.

In later videos we’ll dig deeper into configuration, item and search queries, fragments, integrated GraphQL, and extending and customising GraphQL in the context of Sitecore.

Hope that people find it useful.

Redirects and the Layout Service

In a typical Sitecore ASP.NET server-side rendered solution, redirects are handled by returning a 301 or 302 HTTP status code to the client browser to signal that the client should redirect to a particular URL.

In a Sitecore Headless solution, things might not be quite so simple. For one thing, the Layout Service does not readily provide a way to return a 301/302 redirect status code in the HTTP response from the API. In addition, even if you convinced the Layout Service to return a 301, if you were using the JSS SDK in a JavaScript application running on the client browser, in-app routing might need to handle redirects returned from the API call and identify which route to use and redirect accordingly (in the app itself, not with a full page reload.) Turns out that isn’t as simple as it might appear either.

Both kinds of music – country AND western!

There are at least two distinct types of redirect that we might want to handle in our headless Sitecore application:

  • Redirects when no item or route exists in Sitecore that matches the requested item
  • Redirects when an item exists and its context resolves, but we want it to redirect elsewhere

Redirects where no item exists (these aren’t the items you are looking for)

This is the typical “vanity URL” scenario. For example we might have published a nice, friendly URL for potential customers such as https://mysite.com/newmodels which will redirect to https://mysite.com/catalogues/cars/2021-models. It’s easier to remember and easier for the user to digest. It might also have SEO benefits to use the first URL rather than the second one.

Sure, we can handle this using a CDN or via web server modules like URL Rewrite, but that isn’t very helpful to our digital producers who want to manage redirects themselves in the CMS and publish the new vanity URL alongside a marketing campaign.

This is where modules like the 301 Redirect Module or SXA Redirects come in. Both of these empower your users to manage redirects in the Sitecore tree, but they were not designed to work with Sitecore Headless Services. That said, you could leverage either option, or roll your own, and patch it in. I’ve outlined how you might do that later in this post.

Redirects where an item exists (I resolve therefore I am)

In this scenario we might have a node in the Sitecore content tree that has no content but has descendants that do. Perhaps a Category page that holds all items in a particular category, but that has no actual content itself.

Now you might argue that we shouldn’t have nodes in our IA that have no useful content – and I’d be on your side in that argument! – however some clients would appear to disagree, and so here we are.

Digging into the Layout Service code

Let’s look at each scenario and see how they might be achieved in the Layout Service, as well as some pointers on what to avoid.

Redirects with no Sitecore items

As mentioned earlier, patching into the usual MVC pipelines is no use with the Layout Service because it just blows right past those suckers and does its own thing in a Controller API. The controller that runs the show is in:

Sitecore.LayoutService.Mvc.Controllers.LayoutServiceController

It inherits from System.Web.Mvc.Controller and exposes the “placeholder” and “render” actions in the Layout Service API via MVC controller action methods. The first thing that I looked at was overriding the virtual methods, but after going down a rabbit hole it became apparent that, although overriding the methods would be simple enough, setting up the constructor was more problematic and needed quite a bit of hackery which would prove hard to maintain and upgrade in the future. I then looked into patching the pipeline.

Finding the right place to patch was the key to doing this. Long story short, the place to patch is in the <mvc.requestBegin> pipeline, just after Sitecore.LayoutService.Mvc.Pipelines.RequestBegin.ContextItemResolver like so:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore>
<pipelines>
<mvc.requestBegin>
<processor type="Mysite.Feature.Redirects.Pipelines.RedirectHandler, Mysite.Feature.Redirects" resolve="true"
patch:after="*[@type='Sitecore.LayoutService.Mvc.Pipelines.RequestBegin.ContextItemResolver, Sitecore.LayoutService.Mvc']"/>
</mvc.requestBegin>
</pipelines>
</sitecore>
</configuration>

Then in your RedirectHandler you can add whatever approach floats your boat to identify and process redirects, returning the appropriate 301/302 status code and URL.

However….

As an Australian politician and Prime Minister once said, “Life wasn’t meant to be easy”. In this case there’s a couple of gotchas.

In a JSS application, a client browser requests the initial app from the server and on subsequent requests calls back to the Layout Service API for the route data. This means that there are actually two scenarios to consider: the initial request to the server and the subsequent calls to the API. How you handle this is dependent on your topology and your choice of SDK.

For example, in an SSR setup with a Node.js proxy, your proxy app will need to be able to handle redirects both from the initial request, and on subsequent proxied API requests. In a CSR setup this will be handled by your JSS app. In either case, your JSS app will need to be able to handle HTTP responses with redirect status codes.

Working on this with my front end colleague in an SSR topology using the React JSS SDK, we encountered issues with Axios following 301s without any way of trapping it. It was fine for the initial request, but we were unable to handle Layout Service API calls that returned 301 in the React app. Your mileage may vary, and if you are using .NET rendering host then this should not be an issue.

Another gotcha is CORS. The LayoutServiceController has a bunch of ActionFilterAttributes, one of which is EnableApiKeyCors. This adds some CORS magic to the response and you might need to add this code to the response your redirect handler, depending on your scenario. For reference, you might add something like this to your response code:

HttpResponseBase response = filterContext.HttpContext.Response;
response.Headers["Access-Control-Allow-Origin"] = header;
if (string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Allow-Headers")))
response.AddHeader("Access-Control-Allow-Headers", "*");
if (string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Allow-Credentials")))
response.AddHeader("Access-Control-Allow-Credentials", "true");
if (string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Allow-Credentials")))
response.AddHeader("Access-Control-Expose-Headers", "ETag");
if (!string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Max-Age")))
return;
response.AddHeader("Access-Control-Max-Age", "10");
view raw CorsHeaders.cs hosted with ❤ by GitHub

Phew. This post is longer than I had intended. Nearly there.

Redirects where an item exists

This one is a bit easier. Option one is to have a fight with the customer and convince them to just put some content on the “node with no content”.

If that fails, you could create a custom item type in Sitecore to handle the redirection. I created a NavigationRedirectNode template that had a Treelist field from which you could pick an item from the site tree. Then in the code I just checked for that template ID and returned the redirect. In my case, because of the Axios issues mentioned above, we opted to return the route to which we wanted to redirect via the ItemContext (similar to this post) and handled that in the app. I’m not saying this is the only or best way – it’s just where we ended up.

However! There was a gotcha. The item has to have layout. It doesn’t have to have anything on it, but if you don’t have a Sitecore layout applied to the item then Sitecore jumps in, hijacks the request, and you end up at “The layout for the requested document was not found” with a red screen of doom.

Summary

As you can see, there’s a bit more to redirects when using Sitecore Headless Services compared to a “traditional” Sitecore MVC solution. The above represents my own experiences with applying redirects in a JSS React SSR solution, and is by no means comprehensive, but I hope that it provides some pointers on your own headless travels 🙂

Headless content API options with Sitecore

Harry Potter and the Sorcerer's Stone - Nearly Headless Nick. | Harry  potter movies, Harry potter, Harry potter ghosts

Consuming data from a “headless” CMS is a pretty popular approach these days, as the trend towards the delivery of API-driven front end rendering solutions continues to grow. Sitecore has been increasing its footprint in this space for several years now, and has developed some offerings that leverage the legacy of Sitecore’s content management, personalisation, and analytics features whilst also enabling the delivery of content to headless rendering apps. In this post I outline the options available for delivering and shaping Sitecore content via APIs using the features that Sitecore Headless Services adds on to the base platform, and also mention a couple of methods that are already available without the need for a JSS license.

Headless Services

Sitecore Headless Services (formerly JSS Server Components) provides additional server-side functionality that exposes Sitecore content and rendering information via APIs. Released as part of Sitecore JavaScript Services 9 Tech Preview, nearly four years ago now, it’s moved on considerably since then and provides a number of different methods by which we can extract and query data for consumption by apps using the JSS JavaScript Rendering SDK’s or the ASP.NET Core Rendering SDK, or any solution that needs to consume content from your Sitecore platform. It’s available with an Enterprise license, or as an add-on for other editions, and you can try it out via the Front End Developer Trial program at no cost.

[N.B. The following methods require Headless Services to be installed on your Sitecore instance]

Layout Service

The Layout Service exposes a REST endpoint (or endpoints, you can have multiple) through which content items can be retrieved using an API key and the results are returned in a specific JSON structure. I won’t go into the specifics of it here, but you can read more details in one of my earlier blog posts.

The API is quite flexible and extensible, and you can use a few different approaches to tailor the data to your needs:

Routes

Requesting an item (a route in JSS parlance) from the API returns a fully assembled description of the route content, as well as the renderings assigned to the placeholders on that route. One of the key differentiators of the layout service is that it leverages the Sitecore rendering engine to include rendering information along with content, and this means that you can use Sitecore personalisation and content management techniques to customise the data returned to your headless solution. Thus your digital producers and content authors can take advantage of the tools with which they are already familiar, such as Experience Editor, to add renderings to placeholders, apply rules based personalisation, and create and deploy tests, all of which are fully supported in headless mode when using the API.

The Headless SDKs have been specifically developed to consume the data structures returned by the Layout Serivce, so this is the “go to” option for consuming Sitecore content and rendering information in headless solutions based off the SDKs in both JavaScript or .NET versions.

Placeholders

One lesser known option available in the layout service API is that you can use it to retrieve only the contents of a specific placeholder. This might not sound like much but it is really quite a powerful and useful feature. In a headless delivery model, once the layout data has been retrieved from the API for a given route, the user can continue to interact with the site UI without the need for additional calls to the Sitecore delivery server (unless they request a new route of course.) Using the placeholder API call, you can dynamically retrieve and update the contents of specific placeholders in your headless application based on interactions that your user has had with your site.

For example you could append a querystring to the placeholder API call and personalise the rendering data in the placeholder on the server side, then dynamically update the UI with the freshly customised rendering information. Or you could send data to a custom endpoint, update a goal or a facet, and then pull the personalised rendering data based on the new information about your user. Or perhaps you could lazy load content into your UI to improve performance.

In addition, the JSON returned from the placeholder API call is very lean compared to the route API data, so requests are lightweight and fast.

Rendering contents resolvers

Headless Services introduced the concept of Rendering Contents Resolvers. These are a set of 6 “resolvers” that can be used to tailor the data returned for a specific rendering. The tailored rendering contents are then passed back to the Layout Service. The out-of-the-box resolvers are quite flexible and provide a quick and simple way to tailor rendering contents without the need for custom code. They can be easily extended and you can read more details about how to do that here.

Integrated GraphQL

GraphQL was added to Headless Services and announced at Sitecore Symposium back in 2018, along with the “official” release of JSS. The Sitecore docs describe it as “a generic GraphQL service platform on top of Sitecore. It hosts your data and presents it through GraphQL queries. The API supports real-time data using GraphQL subscriptions.”

What this means in practical terms is that you can query Sitecore items and perform Content Search queries via GraphQL. Integrated GraphQL is the use of GraphQL queries to shape your rendering contents. This is done by simply pasting the query code into a multi-line text field in your rendering (what could go wrong?). This will override any other rendering behaviours and return the query results instead of a datasource, or the output of a rendering contents resolver. GraphQL always wins.

One key difference that should be borne in mind, however, is that the JSON contract returned by an integrated GraphQL query will be quite different to the rendering contracts returned when using out-of-the-box options like rendering contents resolvers. This can result in a bit of a mixed bag of data structures being returned to your headless data consumers, some using the “standard” approach and some using a variety of GraphQL shaped rendering contents.

GraphQL can also be extended. For an example of extending Content Search in GraphQL, see Aaron Bickle’s excellent blog post on the subject.

Layout Service extensions

This is Sitecore: everything is extensible! So it is not difficult to extend the code that powers the API and customise the data contract. You can read about extending the context here.

That pretty much sums up the options for using the Layout Service but it’s not the end of the story. More techniques are available for powering headless solutions.

Other options using Headless Services

Connected GraphQL

This approach uses the same schema as Integrated GraphQL, but exposes API endpoints to which you can send your queries. Using this feature your apps can query Sitecore content and send variables with those queries, perhaps based on client interactions. One example might be to provide a headless search feature, passing user-supplied search terms back to the API endpoint which would in turn use the Content Search API to query Sitecore. Or perhaps use it to retrieve configuration settings or other values on-the-fly without the need to use the Layout Service. Customised GraphQL approaches such as that described in Aaron Bickle’s post mentioned earlier can also be leveraged to customise the default functionality available via Connected GraphQL.

Options that don’t requires Headless Services

Don’t have JSS and Headless Services? Not to worry! There are other options available to feed data to your headless solutions.

JSON renderings

Using SXA? Great! SXA is awesome! This also means that you can use SXA data modelling and JSON renderings and variants to return data to your headless app without a JSS license. This approach is pretty flexible because you have the power of SXA rendering variants and Scriban at your disposal.

Sitecore Services Client

The Sitecore Services Client has been around for quite a while and in a headless scenario it would most likely be used to provide read-only access to items via the ItemService. It’s a flexible API and if you want to simply pull content items out of Sitecore and consume that data in a headless app, then this is a great alternative to Headless Services. One downside is that you don’t get the rendering information that you would have been able to retrieve via the Layout Service, but if you only need items then this is your simplest, best, and cheapest option.

Custom APIs

Finally, you can always create your own API endpoints using .NET. In ASP.NET MVC this will probably be a Controller API or Web API endpoint with custom routing. This approach is very flexible, is something that any .NET developer will be comfortable with creating, and it doesn’t require any additional licenses to serve content.

Summary

No doubt there are other ways to pull content out of Sitecore for consumption in your headless solutions (e.g. the Item WebAPI – does that still exist?) but these are the mainstream approaches. If you want to take full advantage of the headless rendering SDKs and leverage analytics, personalisation, content testing, and the power of Sitecore’s content management feature set (placeholders, renderings, templates, datasources,  etc) then Headless Services is probably the best option, but if you don’t need those features, or if your client/employer doesn’t want to foot the bill for headless, then there are still some solid options available for pulling data out of Sitecore to power your headless solutions.

Serializing rendering parameters in the layout service

The Layout Service currently has limited support for rendering parameters. Parameters are output as name:value pairs which means if you use something like a DropLink as a lookup (e.g. classes, colours, padding or whatever) for rendering parameters then you just get the ID of the item being referenced.

As an example, I’ve created a rendering parameters template for the “Featured Blogs” rendering, which has a single field “Category”, intended to filter blogs being listed to a specific category:

The rendering has been added to the Blog Home page Final Layout and a value set for the Category:

However, out of the box the Layout Service will return the following:

which in some cases might be what you need, but what if you need the target item to which the Droplink field refers? e.g. a CSS class string from a reference data item stored in your site Data folder.

The solution is to override the Initialize pipeline processor in Sitecore.LayoutService.Presentation.Pipelines.RenderJsonRendering and patch it into the Layout Service pipeline configuration.

Patching the Layout Service pipeline processor

Overriding the Initialize method of RenderJsonRendering allows us to extend the RenderingParams property in the RenderedJsonRendering object returned by the pipeline processor with serialized values instead of actual values. (Credit to Jason Woods and the Sitecore Product Team for pointing the way on this one)

using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.LayoutService.Configuration;
using Sitecore.LayoutService.ItemRendering;
using Sitecore.LayoutService.Presentation.Pipelines.RenderJsonRendering;
using Sitecore.LayoutService.Serialization;
using Sitecore.Mvc.Presentation;
using System.Linq;
namespace JSSDemo.Foundation.LayoutService.PipelineProcessors
{
public class CustomInitialize : Initialize
{
IRenderingConfiguration _renderingConfiguration;
public CustomInitialize(IConfiguration configuration) : base(configuration) { }
protected override RenderedJsonRendering CreateResultInstance(RenderJsonRenderingArgs args)
{
_renderingConfiguration = args.RenderingConfiguration;
//Note: the constructor below is different for Sitecore 9.x and 10. The below will only work in Headless Services for Sitecore 10.
return new RenderedJsonRendering()
{
Name = args.Rendering.RenderingItem.Name,
DataSource = args.Rendering.DataSource,
RenderingParams = SerializeRenderingParams(args.Rendering),
Uid = args.Rendering.UniqueId
};
}
protected virtual IDictionary<string, string> SerializeRenderingParams(Rendering rendering)
{
IDictionary<string, string> paramDictionary = rendering.Parameters.ToDictionary(pair => pair.Key, pair => pair.Value);
foreach (string key in paramDictionary.Keys.ToList())
{
if (ID.TryParse(paramDictionary[key], out var itemId))
{
Item item = rendering.RenderingItem.Database.GetItem(itemId);
paramDictionary[key] = _renderingConfiguration.ItemSerializer.Serialize(item, new SerializationOptions() { DisableEditing = true });
}
}
return paramDictionary;
}
}
}

This is then patched into the LayoutService renderJsonRendering pipeline:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&gt;
<sitecore>
<pipelines>
<group groupName="layoutService">
<!– NOTE: This patch will be have a slightly different signature in Headless Services for Sitecore 9.x vs 10. The below works for Headless Services in Sitecore 10 –>
<pipelines>
<renderJsonRendering>
<processor type="JSSDemo.Foundation.LayoutService.PipelineProcessors.CustomInitialize, JSSDemo.Foundation.LayoutService" resolve="true"
patch:instead="*[@type='Sitecore.LayoutService.Presentation.Pipelines.RenderJsonRendering.Initialize, Sitecore.LayoutService']"/>
</renderJsonRendering>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>

In the Featured Blogs example above, the Category Droplink field is now rendered with the data from the target item:

Which is a lot more useful than an item ID 🙂

The code for this post is available in my Github repository along with code for the previous posts on the Layout Service.

NOTE: the above code and patch file will be slightly different between Sitecore 9.x Headless Services and Sitecore 10.