7

I'm working on a 10.2 solution with JSS & NextJS using the GraphQL (edge) endpoint. I already noticed this endpoint is sometimes quite different compared to others and now I would like to extend it. We are also using SXA and I wanted to use a treelist with tokens in the datasource so I created a custom field for that - which is pretty easy to do.

Our problem now is that the data from that field is fetched as string and not as a Multilist field (which would give us an item array). And as GraphQL is used to fetch all data that is an issue...

My first attempt was adding my field in the config (identical as the Treelist) to /sitecore/api/GraphQL/defaults/content/fieldTypeMappings/standardTypeMapping in the config as this is mentioned in the edgeContent schema provider. That doesn't work.

I did find 2 ways I can extend the schema:

  • Apparently all fields are added to a list and attached to a GraphType in Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping.DefaultFieldTypeFactoryStore. I can create my own version of this factory and inject instead - this works but somehow I don't think this is the correct way to extend this as the class doesn't seem to be made to be extended.
  • I was able (as a test) to extend existing GraphType's (eg the ImageFieldGraphType) with extra properties by creating a SchemaExtender. This feels like the more official way to extend things as it is also mentioned in the SC documentation (although not for 10.2). But.. this way I managed to extend existing types, but I don't know how to tell the schema that my "Tokened Treelist" is actually just a MultilistGraphType with such an extender.

So, my question is: how can I use my custom field in the GraphQL schema as Multilist? Can this be done with a SchemaExtender, and if so - how? Should I overwrite the FieldTypeFactoryStore?

1
  • Did you find a solution for your question? I am in similar situation I added a custom Image field and data from that field is fetched as string. Thank you. Commented Mar 1, 2023 at 4:53

2 Answers 2

3

We had a similar challenge some time ago and we contacted Sitecore Support.

Sitecore support summary:

The Edge schema provider doesn't actually use the "fieldTypeMappings" configuration node. The field type mapping is done using a separate "IFieldTypeFactoryStore" service that is registered in "Sitecore.Services.GraphQL.EdgeContent.config". The mapping is hardcoded and doesn't depend on the configuration. The reason behind this is that the Edge preview endpoint should mirror the schema and behavior of Sitecore Experience Edge for XM. If any changes are done to the preview endpoint, it will behave differently from the actual Delivery Experience Edge endpoint which will make the development and testing unreliable.

If you don't plan to use Sitecore Experience Edge for XM, please consider creating a new GraphQL endpoint and using it instead of "edge": https://doc.sitecore.com/xp/en/developers/hd/200/sitecore-headless-development/start-using-sitecore-graphql-api.html This new endpoint can use the default "systemContent" which will allow you to use custom field mapping.

It means that there is no "right and clean" way to achieve what you ask for. The right way will be usage GraphQL endpoint that is based on "content", which is more extendable.

As we don't plan to use Sitecore Experience Edge for XM, we implemented our own Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping.IFieldTypeFactoryStore and added FieldMapping there. This approach works, but doesn't look great.

1

Problem

During my upgrade from Sitecore 9.2 to 10.3, I faced an issue related to the edge endpoint being hardcoded in the Sitecore.LayoutService.GraphQL.LayoutService.GraphQLAwareRenderingContentsResolver. This class is inherited by Sitecore.XA.JSS.Foundation.Presentation.ContentsResolvers.RenderingContentsResolver, which enforces a limitation due to the following code:

// code from Sitecore.LayoutService.GraphQL.LayoutService.GraphQLAwareRenderingContentsResolver that is using f

{
private IGraphQLEndpoint GetPublicEndpoint()
{
    return this._graphQlEndpointManager.GetEndpoints().FirstOrDefault<IGraphQLEndpoint>(e => e.Url.EndsWith("edge"));
}

This implementation limits custom endpoint flexibility by:

  1. Always selecting the default Edge endpoint if enabled.
  2. Requiring that all endpoints must end with "edge".

This was problematic as I needed to implement several custom endpoints.

Working Solution

After reviewing the disassembled Sitecore code, I decided to replace the base renderingContentsResolver type for the sxa-jss configuration. This adjustment allows the use of GraphQL endpoints specified in the site settings.

The configuration that I overrided

<sitecore>
    <layoutService>
      <configurations>
        <config name="sxa-jss">
          <rendering>
            <renderingContentsResolver set:type="Test.Common.SxaJssRenderingContentResolver, Test.Common" resolve="true" />
          </rendering>
        </config>
      </configurations>
    </layoutService>
  </sitecore>

This configuration overrides the default sxa-jss rendering resolver with a custom type. Below is the C# implementation of the custom resolver:


using System;
using System.Globalization;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using Sitecore.Abstractions;
using Sitecore.Configuration;
using Sitecore.Data.Items;
using Sitecore.DependencyInjection;
using Sitecore.JavaScriptServices.Configuration;
using Sitecore.JavaScriptServices.GraphQL.Helpers;

// This namespace contains the correct version of GraphQLAwareRenderingContentsResolver
using Sitecore.JavaScriptServices.GraphQL.LayoutService;
using Sitecore.LayoutService.Configuration;
using Sitecore.LayoutService.Serialization;
using Sitecore.Services.GraphQL.Hosting;
using Sitecore.Services.GraphQL.Hosting.Configuration;
using Sitecore.XA.Foundation.Editing.Service;
using Sitecore.XA.Foundation.Multisite.Extensions;

namespace Test.Common
{
    //This is content resolved was copied from decompiled Sitecore.XA.JSS.Foundation.Presentation.ContentsResolvers but inherited the correct GraphQLAwareRenderingContentsResolver (from JSS namespace)

    public class SxaJssRenderingContentResolver : GraphQLAwareRenderingContentsResolver
    {
        private readonly IControlEditabilityService _controlEditabilityService =
            ServiceLocator.ServiceProvider.GetService<IControlEditabilityService>();


        public SxaJssRenderingContentResolver(
            IConfigurationResolver configurationResolver,
            IGraphQLEndpointManager graphQlEndpointManager,
            IDocumentWriter documentWriter,
            BaseLog log,
            IAsyncHelpers asyncHelpers)
            : base(configurationResolver, graphQlEndpointManager, documentWriter, log, asyncHelpers)
        {
        }

        protected override JObject ProcessItem(
            Item item,
            Sitecore.Mvc.Presentation.Rendering rendering,
            IRenderingConfiguration renderingConfig)
        {
            using (new SettingsSwitcher("Media.AlwaysIncludeServerUrl",
                       IncludeServerUrlInMediaUrls.ToString(CultureInfo.InvariantCulture)))
            {
                if (!rendering.Item.IsInSxaContext())
                    return JObject.Parse(renderingConfig.ItemSerializer.Serialize(item));
                var controlEditability =
                    _controlEditabilityService.GetControlEditability(
                        new Sitecore.XA.Foundation.Mvc.Wrappers.Rendering(rendering));
                return JObject.Parse(renderingConfig.ItemSerializer.Serialize(item,
                    new SerializationOptions
                    {
                        DisableEditing = !controlEditability.Editable
                    }));
            }
        }

        public override object ResolveContents(
            Sitecore.Mvc.Presentation.Rendering rendering,
            IRenderingConfiguration renderingConfig)
        {
            var contents = base.ResolveContents(rendering, renderingConfig) as JObject;
            if (contents != null &&
                rendering.Item.IsInSxaContext() &&
                !_controlEditabilityService
                    .GetControlEditability(new Sitecore.XA.Foundation.Mvc.Wrappers.Rendering(rendering))
                    .Editable)
                contents.Descendants()
                    .Where(d => d.Path.EndsWith(".editable", StringComparison.OrdinalIgnoreCase) && d.HasValues)
                    .ToList()
                    .ForEach(e => contents.SelectToken(e.Path)?.Parent?.Remove());
            return contents;
        }
    }
}

This change enabled the use of custom GraphQL endpoints in our multi-site/multi-tenant solution, providing the necessary flexibility for our implementation.

Conclusion

Implementing this solution allowed me to use multiple custom GraphQL endpoints effectively in our multi-site/multi-tenant Sitecore setup. It was a challenging problem to solve, but very rewarding once resolved. I hope this detailed walkthrough saves time for anyone facing similar issues - because I certainly spent a lot figuring it out! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.