Sitecore Headless – How to reuse Layout Service serialization in custom API Controllers

On one of our current project we have a close deadline, therefore we need to simplify implementation and cut from the scope as much as possible to roll out the MVP with the full functionality but without Experience Editor support – as it’s not priority for now. We had a long discussion about this, the main reason of cutting scope is to keep the frontend simple as possible and focus on business logic functionality instead of editing experience. Therefore on the Sitecore side we are providing a few APIs for the frontend and not creating any layout, rendering and placeholders; only pure templates and content.

After the release of MVP we would like to start to convert components and layouts to Sitecore components using custom Contents Resolvers and we want this transition to be done as less code change as possible on the Sitecore side.

My idea is to use the processing and serializing of content implementation from the Layout Service, the only difference we want to use it in our custom API controllers. It turned out it’s pretty easy, the following steps should be done to achieve it.

1. Default reusable contents resolver

First we need to make the default RenderingContentsResolver available for controllers – especially the ProcessItem method. But unfortunately that is a protected method, so we need to open it with a wrapper.

using Newtonsoft.Json.Linq;
using Sitecore.Data.Items;
using Sitecore.LayoutService.Configuration;
using Sitecore.LayoutService.ItemRendering.ContentsResolvers;
namespace Foundation.ContentsResolvers
{
public interface IDefaultRenderingContentsResolver : IRenderingContentsResolver
{
JObject ProcessItem(Item item, IRenderingConfiguration renderingConfig);
}
/// <summary>
/// This implementation is needed to able to use item serialization out of the Content Resolver context
/// </summary>
public class DefaultRenderingContentsResolver : RenderingContentsResolver, IDefaultRenderingContentsResolver
{
public DefaultRenderingContentsResolver()
{
this.IncludeServerUrlInMediaUrls = false;
}
public JObject ProcessItem(Item item, IRenderingConfiguration renderingConfig)
{
return base.ProcessItem(item, null, renderingConfig);
}
}
}

2. Base controller

Let’s create a base controller which holds all of the necessary dependencies and add the [RequireSscApiKey] attribute. This attribute is protecting the whole controller with the same API key as the Layout Service. This is not necessary, but we want to protect our endpoints the same way as the Layout Service does.

using Foundation.ContentsResolvers;
using Sitecore.LayoutService.Configuration;
using Sitecore.LayoutService.Mvc.Security;
using Sitecore.Mvc.Controllers;
namespace Foundation.Controllers
{
[RequireSscApiKey]
public class BaseApiController : SitecoreController
{
protected readonly IDefaultRenderingContentsResolver defaultRenderingContentsResolver;
protected readonly IRenderingConfiguration renderingConfiguration;
public BaseApiController(
IDefaultRenderingContentsResolver defaultRenderingContentsResolver,
IConfiguration configuration)
{
this.defaultRenderingContentsResolver = defaultRenderingContentsResolver;
this.renderingConfiguration = configuration.GetNamedConfiguration(configuration.DefaultConfigurationName)?.RenderingConfiguration;
}
}
}

3. Dependency injection

Nothing special, we just need to register the DefaultRenderingContentsResolver.

using Foundation.ContentsResolvers;
using Foundation.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;
namespace Feature.DI
{
public class RegisterContainer : IServicesConfigurator
{
public void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<IDefaultRenderingContentsResolver, DefaultRenderingContentsResolver>();
serviceCollection.AddMvcControllers("*.Feature");
}
}
}

4. Example controller implementation

Last but not least, an example of how to to use the ProcessItem method.

using System.Net;
using System.Web.Mvc;
using Foundation.ContentsResolvers;
using Sitecore;
using Sitecore.LayoutService.Configuration;
namespace Feature.Controllers
{
public class CustomApiController : BaseApiController
{
public CustomApiController(
IDefaultRenderingContentsResolver defaultRenderingContentsResolver,
IConfiguration configuration)
: base(renderingConfigFactory, defaultRenderingContentsResolver, configuration)
{
}
[HttpGet]
public ActionResult Organizations()
{
var item = Context.Database.GetItem("/your/test/item/path");
var jsonResult = defaultRenderingContentsResolver.ProcessItem(i, renderingConfiguration)
return Content(jsonResult.ToString(), "application/json");
}
}
}

Result 🚀

As a JSON result you will see something like this, which is compared to the Layout Service response is pretty identical if we compare the component level serialized results inside the placeholders property.

{
"fields": {
"Title": {
"value": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
},
"Image": {
"value": {
"src": "/-/media/default-website/cover.jpg?h=550&iar=0&w=1600&hash=96C7D407539A5C08E994AA1A58BF3A07",
"alt": "",
"width": "1600",
"height": "550"
}
},
"File": {
"value": {
"src": "/-/media/default-website/cover.jpg",
"name": "cover",
"displayName": "cover",
"title": "",
"keywords": "",
"description": "",
"extension": "jpg",
"mimeType": "image/jpeg",
"size": "119719"
}
}
}
}
view raw response.json hosted with ❤ by GitHub

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s