Reusable schema referenced pages are empty

Hello,

We have a reusable field schema for all links in our site. The idea is that we allow editors to specify the type of link and we display a different input type depending on the selection

1.00

This worked fine when the content item selector above only had 1 content type configured, PageType_Page. The generated property in all content types that use the reusable schema was

public IEnumerable<PageType_Page> Page { get; set; }

However, this is a new project and we are adding new page types. We added a page type called PageType_Homepage and when the content item selector was updated to now support multiple content types the code above changed to

public IEnumerable<IWebPageFieldsSource> Page { get; set; }

which is understandable.

We are using the ContentItemQueryBuilder to get content but now the Page property always has a count of 0, the referenced page is not being returned.

My initial question is if we are using reusable schemas in a way not intended? Although if we moved the property directly to a content type I fell like the same issue would happen

I have tried adding IncludeWebPageData() into my queries but that doesn't work
Has anyone run into this issue before whereby you want to have a content item that can link to pages of multiple types but the query is not returning the referenced pages?

Do we have to rethink our content model?


Environment

  • Xperience by Kentico version: [30.12.1]

  • .NET version: [8]

  • Execution environment: [SaaS]

Tags:
Content modeling Content querying
0

Answers

Yes, the page property on the Generated model has 0 items, its an IEnumerable, even though the content item is linked correctly to a page

Here is an example of a method where I'm getting a content item by a particular field value, i have similar methods for getting by Id, Guid, etc.

    public async Task<T> GetReusableContentItemByFieldAsync<T>(
        string contentTypeName,
        string field,
        string value,
        string localization,
        int maxLevel,
        CancellationToken cancellationToken = default) where T : new()
    {
        var builder = new ContentItemQueryBuilder()
            .ForContentType(
                contentTypeName,
                config =>
                {
                    config.Where(x =>
                    {
                        x.WhereEquals(field, value);
                    }).TopN(1);
                    config.WithLinkedItems(maxLevel);
                })
            .InLanguage(localization);

        var contentItemResult = await _executor.GetResult(builder: builder,
            resultSelector: container => _queryMapper.Map<T>(container),
            options: new ContentQueryExecutionOptions() { ForPreview = _channelContext.IsPreview },
            cancellationToken: cancellationToken);

        return contentItemResult.FirstOrDefault() ?? new T();
    }

Also a similar method to get the current page which is referencing CTA items which in turn reference the reusable schema

    public async Task<T?> GetCurrentPageAsync<T>(
        int maxLevel = ApplicationConstants.ContentDefaults.DefaultMaxLevel,
        CancellationToken cancellationToken = default) where T : new()
    {
        var currentPage = _webPageDataContextRetriever.Retrieve().WebPage;
        var builder = new ContentItemQueryBuilder()
            .ForContentType(currentPage.ContentTypeName,
                config => config.ForWebsite(currentPage.WebsiteChannelName)
                .Where(where => where.WhereEquals(nameof(IWebPageContentQueryDataContainer.WebPageItemID), currentPage.WebPageItemID))
                .WithLinkedItems(maxLevel))
            .InLanguage(currentPage.LanguageName);

        var pageResult = await _executor.GetWebPageResult(builder: builder,
            resultSelector: container => _queryMapper.Map<T>(container),
            options: new ContentQueryExecutionOptions() { ForPreview = _channelContext.IsPreview },
            cancellationToken: cancellationToken);

        return pageResult.FirstOrDefault() ?? default;
    }

When the LinkType is File or URL then these values are populated, which makes sense as they are just strings. The Page property is always empty

This is my reusable schema

	public interface IRsfhLinkFields
	{
		/// <summary>
		/// Code name of the reusable field schema.
		/// </summary>
		public const string REUSABLE_FIELD_SCHEMA_NAME = "Rsfh.LinkFields";


		/// <summary>
		/// LinkType.
		/// </summary>
		public string LinkType { get; set; }


		/// <summary>
		/// Page.
		/// </summary>
		public IEnumerable<IWebPageFieldsSource> Page { get; set; }


		/// <summary>
		/// URL.
		/// </summary>
		public string URL { get; set; }


		/// <summary>
		/// FileItem.
		/// </summary>
		public IEnumerable<ContentType_File> FileItem { get; set; }
	}

If I change the Page property to

public IEnumerable<PageType_Page> Page { get; set; } 

then it works as expected.

I'm guessing that I'm missing something in my queries but can't work it out. I even tried Claude with the documentation MCP server

0

Why aren't you using the Content Retriever? It's available in v30.12.0 (it was added in April 2025) and would simplify a lot of that code you've shared.

Additionally, ForContentType is the original content querying API in Xperience by Kentico - it doesn't have many features added later to the improved ForContentTypes API.

I also see you are using an explicit query mapper. We added two methods to IContentQueryExecutor to do this query mapping for you - GetMappedWebPageResult and GetMappedResult. Both of these features were added back in March 2024.


To reproduce your scenario, I created a complex model like the one you are describing, with a RFS that had a "Pages and reusable content" type field that accepted 2 different reusable content types and 1 web page content type. I then assigned this RFS to the ArticlePage in DancingGoat

Reusable content type: Link Simple

Reusable content type: Link Complex

RFS: LinkFields > Links (field) > [Link Simple, Link Complex, Landing Page]

Article Page: ArticleTitle, ArticlePageSummary, ..., LinkFields

The ArticlePage content type C# class is generated with a property IEnumerable<IContentItemFieldsSource> Links

I queried for an Article by GUID or just the "Current Page" using the IContentRetriever - both worked perfectly with all article page fields, the RFS field, and the linked content items fully populated.

// Works on an article page
var article = await contentRetriever.RetrieveCurrentPage<ArticlePage>(
    new RetrieveCurrentPageParameters { LinkedItemsMaxLevel = 3 }
);

// Works anywhere
var articles = await contentRetriever.RetrievePagesByGuids<ArticlePage>(
    [article.SystemFields.WebPageItemGUID],
    new RetrievePagesParameters { LinkedItemsMaxLevel = 3 },
);

Because I'm using the IContentRetriever all the "context" is handled for me - language, preview mode, current page - and all the mapping is done automatically.

My recommendation... use the IContentRetriever.

0

Thanks Sean, the Content Retriever looks like a better option for sure. I updated my GetCurrentPageAsync method as you suggested. When testing I set the max level to 5 just to be sure

    public async Task<T?> GetCurrentPageAsync<T>(
        int maxLevel = ApplicationConstants.ContentDefaults.DefaultMaxLevel,
        CancellationToken cancellationToken = default) where T : class, IWebPageFieldsSource, new()
    {
        try
        {
            var currentPage = await _contentRetriever.RetrieveCurrentPage<T>(
                new RetrieveCurrentPageParameters
                {
                    LinkedItemsMaxLevel = maxLevel,
                    IsForPreview = _channelContext.IsPreview
                },
                additionalQueryConfiguration: null,
                cacheSettings: new RetrievalCacheSettings(
                    cacheItemNameSuffix: $"GetCurrentPageAsync|{typeof(T).Name}|{maxLevel}"),
                cancellationToken: cancellationToken);

            return currentPage;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving current page with max level {MaxLevel}", maxLevel);
            return default;
        }
    }

However, I still see the same issue. I wonder if my content model structure is incorrect i.e. is this a valid thing to do?

My structure is that I have a page type PageType_Homepage, this references a list of reusable content item homepage buttons i.e. ContentType_HomepageHeroButton and these reference the RSF IRsfhLinkFields. The RSF references a page. a string URL and a reusable content item ContentType_File. The file is populated without issue if the link type is File. The URL is populated without issue if the link type is URL. If the link type is Internal and a page is referenced in the admin, the Page property is an IEnumerable with count = 0

PageType_Homepage

public partial class PageType_Homepage : IWebPageFieldsSource, IRsfhPageBehaviour, IRsfhSeo
{
	/// <summary>
	/// Code name of the content type.
	/// </summary>
	public const string CONTENT_TYPE_NAME = "Rsfh.PageType_Homepage";


	/// <summary>
	/// Represents system properties for a web page item.
	/// </summary>
	[SystemField]
	public WebPageFields SystemFields { get; set; }


	/// <summary>
	/// HomepageTitle.
	/// </summary>
	public string HomepageTitle { get; set; }


	/// <summary>
	/// PageSearchSummaryText.
	/// </summary>
	public string PageSearchSummaryText { get; set; }


	/// <summary>
	/// HiddenH1.
	/// </summary>
	public string HiddenH1 { get; set; }


	/// <summary>
	/// HeroBannerTitle.
	/// </summary>
	public string HeroBannerTitle { get; set; }


	/// <summary>
	/// HeroBannerImage.
	/// </summary>
	public IEnumerable<ContentType_Image> HeroBannerImage { get; set; }


	/// <summary>
	/// ImageAltTextOverride.
	/// </summary>
	public string ImageAltTextOverride { get; set; }


	/// <summary>
	/// SearchBarLabel.
	/// </summary>
	public string SearchBarLabel { get; set; }


	/// <summary>
	/// SearchBarPlaceholderText.
	/// </summary>
	public string SearchBarPlaceholderText { get; set; }


	/// <summary>
	/// HomepageButtons.
	/// </summary>
	public IEnumerable<ContentType_HomepageHeroButton> HomepageButtons { get; set; }


	/// <summary>
	/// IntroductoryText.
	/// </summary>
	public string IntroductoryText { get; set; }


	/// <summary>
	/// HideFromSearch.
	/// </summary>
	public bool HideFromSearch { get; set; }


	/// <summary>
	/// HideFromSideNav.
	/// </summary>
	public bool HideFromSideNav { get; set; }


	/// <summary>
	/// HideFromHtmlSitemap.
	/// </summary>
	public bool HideFromHtmlSitemap { get; set; }


	/// <summary>
	/// HideFromXmlSitemap.
	/// </summary>
	public bool HideFromXmlSitemap { get; set; }


	/// <summary>
	/// HideFromExternalSearch.
	/// </summary>
	public bool HideFromExternalSearch { get; set; }


	/// <summary>
	/// SeoTitle.
	/// </summary>
	public string SeoTitle { get; set; }


	/// <summary>
	/// SeoDescription.
	/// </summary>
	public string SeoDescription { get; set; }


	/// <summary>
	/// SeoKeywords.
	/// </summary>
	public string SeoKeywords { get; set; }


	/// <summary>
	/// OpenGraphTitle.
	/// </summary>
	public string OpenGraphTitle { get; set; }


	/// <summary>
	/// OpenGraphDescription.
	/// </summary>
	public string OpenGraphDescription { get; set; }


	/// <summary>
	/// OpenGraphImage.
	/// </summary>
	public IEnumerable<ContentType_Image> OpenGraphImage { get; set; }


	/// <summary>
	/// XTitle.
	/// </summary>
	public string XTitle { get; set; }


	/// <summary>
	/// XDescription.
	/// </summary>
	public string XDescription { get; set; }


	/// <summary>
	/// XImage.
	/// </summary>
	public IEnumerable<ContentType_Image> XImage { get; set; }
}

ContentType_HomepageHeroButton

public partial class ContentType_HomepageHeroButton : IContentItemFieldsSource, IRsfhLinkFields
{
	/// <summary>
	/// Code name of the content type.
	/// </summary>
	public const string CONTENT_TYPE_NAME = "Rsfh.ContentType_HomepageHeroButton";


	/// <summary>
	/// Represents system properties for a content item.
	/// </summary>
	[SystemField]
	public ContentItemFields SystemFields { get; set; }


	/// <summary>
	/// LinkText.
	/// </summary>
	public string LinkText { get; set; }


	/// <summary>
	/// Icon.
	/// </summary>
	public IEnumerable<ContentType_Icon> Icon { get; set; }


	/// <summary>
	/// LinkType.
	/// </summary>
	public string LinkType { get; set; }


	/// <summary>
	/// Page.
	/// </summary>
	public IEnumerable<IWebPageFieldsSource> Page { get; set; }


	/// <summary>
	/// URL.
	/// </summary>
	public string URL { get; set; }


	/// <summary>
	/// FileItem.
	/// </summary>
	public IEnumerable<ContentType_File> FileItem { get; set; }
}

ContentType_File

	public partial class ContentType_File : IContentItemFieldsSource
	{
		/// <summary>
		/// Code name of the content type.
		/// </summary>
		public const string CONTENT_TYPE_NAME = "Rsfh.ContentType_File";


		/// <summary>
		/// Represents system properties for a content item.
		/// </summary>
		[SystemField]
		public ContentItemFields SystemFields { get; set; }


		/// <summary>
		/// File.
		/// </summary>
		public ContentItemAsset File { get; set; }
	}

IRsfhLinkFields

public interface IRsfhLinkFields
{
	/// <summary>
	/// Code name of the reusable field schema.
	/// </summary>
	public const string REUSABLE_FIELD_SCHEMA_NAME = "Rsfh.LinkFields";


	/// <summary>
	/// LinkType.
	/// </summary>
	public string LinkType { get; set; }


	/// <summary>
	/// Page.
	/// </summary>
	public IEnumerable<IWebPageFieldsSource> Page { get; set; }


	/// <summary>
	/// URL.
	/// </summary>
	public string URL { get; set; }


	/// <summary>
	/// FileItem.
	/// </summary>
	public IEnumerable<ContentType_File> FileItem { get; set; }
}
0

.WithLinkedItems(5) seems like it should capture all the linked items in your design. This might be a limitation in how many layers that linking process translates when the leaf content item is a web page because that scenario requires JOIN'ing back several other additional tables for URLs, web page details, etc...

You can try breaking this query into 2 parts

  1. Get your home page with buttons, but don't include their linked items (.WithLinkedItems(2)).
  2. Then, do a separate IContentRetriever query with an added .WhereIn() with the Buttons.SystemFields.ContentItemID to retrieve all the buttons with their linked items (.WithLinkedItems(2)).

Note that this is sometimes similar to what the IContentRetriever does internally - it's not always doing massive JOINs and subqueries.

If this resolves your issue, report the scenario as a bug to support. I think there might be some future improvements to content querying planned to handle some of these edge cases.

0

To response this discussion, you have to login first.