Query web pages based on tags assigned to the content items assigned to the web pages

2024/12/12 4:52 PM

How do we query web pages based on tags assigned to the content items of the assigned to the web pages using the ContentItemSelector


Environment

  • Xperience by Kentico version: 29.7.0
  • .NET version: 8
  • Execution environment: Local

Answers

2024/12/12 5:24 PM

Standard Method would be a 2 step processed:

  1. Get the Web Pages found in the ContentItemSelector (you can probably keep it at 1 level since you're only interested in the Tags)
  2. Select all the distinct taxonomy GUIDs from those web page item's taxonomy fields
  3. Generate a Content Item Query of each type you want, using those taxonomy tags.

It's a little convoluted because Taxonomy fields can vary with each type. Here's a code sample:

// Pretend these Guids are from the ContentItemSelector
IEnumerable<ContentItemReference> selectedContentItems = [];
var selectorContentItemGuids = selectedContentItems.Select(x => x.Identifier);

var gettingTagsQuery = new ContentItemQueryBuilder()
    .ForContentTypes(x => x.WithLinkedItems(0)
        .OfReusableSchema([PossibleReusableSchemaWithTags]) // Optional
    )
    .Parameters(x => x.Where(where =>
        where.WhereIn(nameof(ContentItemFields.ContentItemGUID), selectorContentItemGuids))
    );
// Get the items, if you use a REusable Schema then you can possibly parse each item as that reusable schema to return those vs. doing the next mapping step.
var myItems = await _contentQueryExecutor.GetResult(gettingTagsQuery, contentItemDataContainer => contentItemDataContainer);

// Now we need to map each type so we can get the right fields
var tagGuids = myItems.SelectMany(x => {
    return x.ContentItemName switch {
        BasicPage.CONTENT_TYPE_NAME => _contentQueryResultMapper.Map<BasicPage>(x).MyTagFields.Select(x => x.Identifier),
        Home.CONTENT_TYPE_NAME => _contentQueryResultMapper.Map<Home>(x).MyOtherTagsField.Select(x => x.Identifier),
        _ => []
    };
}).Distinct();

// Now you can do your query on other items with the tags.
var actualItemsQuery = new ContentItemQueryBuilder()
    .ForContentType(MyRelatedItemType.CONTENT_TYPE_NAME, query => query.Where(where => where.WhereContainsTags(nameof(MyRelatedItemType.TheTaxonomyField), tagGuids)))
    .ForContentType(MyOtherRelatedItemType.CONTENT_TYPE_NAME, query => query.Where(where => where.WhereContainsTags(nameof(MyOtherRelatedItemType.TheTaxonomyField), tagGuids)));

// Get items and do whatever mapping
var actualItems = await _contentQueryExecutor.GetResult(actualItemsQuery, x => x);

If you elect to use the XperienceCommunity.DevTools.RelationshipsExtended (not yet published but almost there) then you can opt to use the ContentID to TagID binding table, in which case the logic gets a lot simpler:

            // Pretend these Guids are from the ContentItemSelector
            IEnumerable<ContentItemReference> selectedContentItems = [];
            var selectorContentItemGuids = selectedContentItems.Select(x => x.Identifier);

            var categoryDataQuery = $@"select OtherCIC.ContentItemCategoryContentItemID from RelationshipsExtended_ContentItemCategory CIC
inner join CMS_ContentItem C on C.ContentItemID = CIC.ContentItemCategoryContentItemID
inner join RelationshipsExtended_ContentItemCategory OtherCIC on OtherCIC.ContentItemCategoryTagID = CIC.ContentItemCategoryTagID
where C.ContentItemGUID in ('{string.Join("','", selectorContentItemGuids)}')";

            // Now you can do your query on other items with the tags.
            var actualItemsQuery = new ContentItemQueryBuilder()
                .ForContentType(MyRelatedItemType.CONTENT_TYPE_NAME, query => query.Where((where) => where.WhereIn("ContentItemID", new DataQuery(categoryDataQuery))))
                .ForContentType(MyOtherRelatedItemType.CONTENT_TYPE_NAME, query => query.Where(where => where.WhereIn("ContentItemID", new DataQuery(categoryDataQuery))));
2024/12/13 5:26 PM

What we came up with was pretty similar. We didn't need quite as complex of a solution as what you provided. The only downside is that there is not currently the ability to run this with an or Where clause. Theoretically it would be nice to also pull in the parent items if they would happen to have the categories assigned to them as well.

  • So, pull in child items based on the tags
  • grab the fields needed for the linked schemas
  • Go and get the pages based on the linked schema field.
var childItems = new ContentItemQueryBuilder()
    .ForContentType(ChildItem.CONTENT_TYPE_NAME, options => options
        .Columns(nameof(IContentQueryDataContainer.ContentItemID), nameof(IContentQueryDataContainer.ContentItemCommonDataContentLanguageID))
        .Where(x => x.WhereContainsTags("Category_Legacy", tagIdentifiers)));

var results = await contentQueryExecutor.GetMappedResult<ChildItem>(childItems);

var guids = results.Select(x=>x.SystemFields);

var builder = new ContentItemQueryBuilder()
    .ForContentType(Page.CONTENT_TYPE_NAME, config => config
        .ForWebsite(websiteChannelContext.WebsiteChannelName)
        .WithLinkedItems(1)
        .LinkedFromSchemaField(nameof(Page.SelectedChild), guids));


// Retrieves the data of the selected content type
return await contentQueryExecutor.GetMappedResult<Page>(builder);

Note, this code has not been tested as of yet ;) so may be corrected in the in near future.

To answer this question, you have to login first.