Minesh Shah (Netcel)
Oct 4, 2022
  3198
(2 votes)

Display Child Pages in Content Delivery API Response

The below example will implement an instance of IContentConverterProvider to customise the serialisation of PageData and output child pages in the Content Delivery API response. It is a very simple implementation although can be extended to fully customise the Content Delivery Response.

All code below can be found on the following GIT Repo : https://github.com/Netcel-Optimizely/Optimizely-ContentDelivery-Examples

Firstly let’s create an interface which can be implemented for specific conversion scenarios

    /// <summary>
    /// Content api model property convertor
    /// </summary>
    public interface IContentApiModelConvertor 
    {
        /// <summary>
        /// Convert content to api model
        /// </summary>
        /// <param name="content"></param>
        /// <param name="contentApiModel"></param>
        /// <param name="converterContext"></param>
        void Convert(IContent content, ContentApiModel contentApiModel, ConverterContext converterContext);
    }

Reference : https://github.com/Netcel-Optimizely/Optimizely-ContentDelivery-Examples/blob/main/src/Netcel.ContentDelivery/Interfaces/IContentApiModelConvertor.cs 

To handle the conversion, we must create our own Content Converter for this create a new class called PageContentConvertor which inherits DefaultContentConverter this already implements IContentConverter and deals with the base conversion logic.

    public class PageContentConvertor : DefaultContentConverter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IEnumerable<IContentApiModelConvertor> _contentApiModelConvertors;

        /// <summary>
        /// Initializes a new instance of the <see cref="PageContentConvertor"/> class.
        /// </summary>
        public PageContentConvertor()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PageContentConvertor"/> class.
        /// Default page convertor
        /// </summary>
        /// <param name="contentTypeRepository"></param>
        /// <param name="reflectionService"></param>
        /// <param name="contentModelService"></param>
        /// <param name="contentVersionRepository"></param>
        /// <param name="contentLoaderService"></param>
        /// <param name="urlResolverService"></param>
        /// <param name="propertyConverterResolver"></param>
        /// <param name="httpContextAccessor"></param>
        /// <param name="contentApiModelConvertors"></param>
        public PageContentConvertor(
            IContentTypeRepository contentTypeRepository,
            ReflectionService reflectionService,
            IContentModelReferenceConverter contentModelService,
            IContentVersionRepository contentVersionRepository,
            ContentLoaderService contentLoaderService,
            UrlResolverService urlResolverService,
            IPropertyConverterResolver propertyConverterResolver,
            IHttpContextAccessor httpContextAccessor,
            IEnumerable<IContentApiModelConvertor> contentApiModelConvertors)
            : base(
                contentTypeRepository,
                reflectionService,
                contentModelService,
                contentVersionRepository,
                contentLoaderService,
                urlResolverService,
                propertyConverterResolver)
        {
            _httpContextAccessor = httpContextAccessor;
            _contentApiModelConvertors = contentApiModelConvertors;
        }

        /// <summary />
        /// <param name="content"></param>
        /// <param name="converterContext"></param>
        /// <returns></returns>
        public override ContentApiModel Convert(IContent content, ConverterContext converterContext)
        {
            if (converterContext.ContextMode.EditOrPreview())
            {
                _httpContextAccessor.HttpContext.SetupVisitorGroupImpersonation(content, AccessLevel.Read);
            }

            var model = base.Convert(content, converterContext);

            foreach (var convertor in _contentApiModelConvertors)
            {
                convertor.Convert(content, model, converterContext);
            }

            if (converterContext.Options.FlattenPropertyModel)
            {
                FlattenPropertyMap(model);
            }

            return model;
        }
    }

Reference : https://github.com/Netcel-Optimizely/Optimizely-ContentDelivery-Examples/blob/main/src/Netcel.ContentDelivery/ContentConvertor/PageContentConvertor.cs

Register this Class at Startup

services.AddSingleton<PageContentConvertor>();

Now, let’s create our own PageContentConvertorProvider which implements IContentConverterProvider

    public class PageContentConvertorProvider : IContentConverterProvider 
    {
        private readonly PageContentConvertor _pageContentConvertor;

        /// <summary>
        /// Initializes a new instance of the <see cref="PageContentConvertorProvider"/> class.
        /// </summary>
        /// <param name="pageContentConvertor"></param>
        public PageContentConvertorProvider(PageContentConvertor pageContentConvertor)
        {
            _pageContentConvertor = pageContentConvertor;
        }

        public int SortOrder => 200;

        /// <summary>
        /// Resolve custom page convertor
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public IContentConverter Resolve(IContent content)
        {
            // further enhance this to allow different supported convertors
            return content is PageData ? _pageContentConvertor : null;
        }
    }

Reference: https://github.com/Netcel-Optimizely/Optimizely-ContentDelivery-Examples/blob/main/src/Netcel.ContentDelivery/ContentConvertorProviders/PageContentConvertorProvider.cs 

We also register at Startup 

 services.TryAddEnumerable(ServiceDescriptor.Scoped<IContentApiModelConvertor, PageDataApiModelConvertor>());

Finally for the actual business logic to perform the conversion we first need a Model for the Navigation Item

    /// <summary>
     /// Navigation Item Dto.
     /// </summary>
    public class NavigationItem
    {
        /// <summary>
        /// Gets or sets navigation Title.
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Gets or sets navigation Url.
        /// </summary>
        public string Url { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether true if it matches the current page.
        /// </summary>
        public bool IsSelected { get; set; }

        /// <summary>
        /// Is Visibly in Menu Property Set
        /// </summary>
        public bool VisibleInMenu { get; set; }

        public IEnumerable<NavigationItem> Children { get; set; } = Enumerable.Empty<NavigationItem>();
    }

Reference: https://github.com/Netcel-Optimizely/Optimizely-ContentDelivery-Examples/blob/main/src/Netcel.ContentDelivery/Models/NavigationItem.cs 

We than create our Page Model Convertor which contains the business logic to output the Child Pages to Content Delivery API

    /// <summary>
    /// Page Data Content Convertor
    /// </summary>
    public class PageDataApiModelConvertor : IContentApiModelConvertor
    {
        private readonly IContentLoader _contentLoader;
        private readonly IUrlResolver _urlResolver;

        /// <summary>
        /// Initializes a new instance of the <see cref="PageDataApiModelConvertor"/> class.
        /// </summary>
        /// <param name="contentLoader"></param>
        /// <param name="urlResolver"></param>
        public PageDataApiModelConvertor(IContentLoader contentLoader, IUrlResolver urlResolver)
        {
            _contentLoader = contentLoader;
            _urlResolver = urlResolver;
        }

        public void Convert(IContent content, ContentApiModel contentApiModel, ConverterContext converterContext)
        {
            if (content is not PageData pageData)
            {
                return;
            }

            // add navigation
            var navigation = new List<NavigationItem>();
            var children = _contentLoader.GetChildren<PageData>(content.ContentLink);
            navigation.AddRange(children.Select(x => CreateNavigationStructure(x, pageData)));
            contentApiModel.Properties.Add("ChildPages", navigation);


        }

        private NavigationItem CreateNavigationStructure(PageData page, IContent currentContent)
        {
            var model = CreateNavigationItem(page, currentContent);
            var children = _contentLoader.GetChildren<PageData>(page.ContentLink);
            model.Children = children.Select(x => CreateNavigationStructure(x, currentContent));

            return model;
        }

        private NavigationItem CreateNavigationItem(PageData pageContent, IContent currentContent)
        {
            return new NavigationItem
            {
                Title = pageContent.Name,
                Url = _urlResolver.GetUrl(pageContent.ContentLink),
                IsSelected = pageContent.ContentLink == currentContent.ContentLink,
                VisibleInMenu = pageContent.VisibleInMenu,
            };
        }
    }

Reference : https://github.com/Netcel-Optimizely/Optimizely-ContentDelivery-Examples/blob/main/src/Netcel.ContentDelivery/ContentApiModelConvertors/PageDataApiModelConvertor.cs 

Register the conversion provider at Startup 

 services.TryAddEnumerable(ServiceDescriptor.Singleton<IContentConverterProvider, PageContentConvertorProvider>());

If all goes to plan when querying any page via the Content Delivery API the payload of the response should include the child pages.

e.g.

Oct 04, 2022

Comments

Please login to comment.
Latest blogs
Controlling Optimizely Forms Cookie Expiration in .NET Core

Learn how to make Optimizely Forms cookies behave as session cookies in CMS 12+ (.NET Core) using a simple middleware - and why the official...

Henning Sjørbotten | Jun 19, 2026 |

Opal: Beyond the Obvious

A couple of less-obvious ways I'm using Optimizely's Opal to automate real work across Salesforce, GitHub, and Teams.

Daniel Isaacs | Jun 18, 2026 |

EOL Announcement for Preview3 CMS REST API

Action Required: Migrate Preview3 API Integrations to CMS REST API v1 Before August 1, 2026 If your SaaS CMS applications are currently using the...

Kathy Copeland | Jun 18, 2026

Environmental DXP badges

I recently released my first Optimizely add-on in a while, that was based on some client feedback on the Optimizely DXP – they wanted to be able to...

Matt Pallatt | Jun 18, 2026

CI/CD deployment failure in Optimizely DXP Passportal. .NET 10 and GitHub Runner Compatibility

.NET 10 and GitHub Runner Compatibility With the release of .NET 10, the windows-latest and ubuntu-latest GitHub Actions runners may use the latest...

owaiskhan | Jun 16, 2026

Integrating Optimizely CMP Assets into an Existing Document Listing Page

Integrating Optimizely CMP Assets into an Existing Document Listing Page In some projects, there may be a requirement to retrieve assets from...

owaiskhan | Jun 16, 2026