Indexing blocklist data in Umbraco 9

jemayn

Jesper Mayntzhusen

Posted on March 14, 2022

Indexing blocklist data in Umbraco 9

Here is a short example on how to create a new index field and then index specific data to that field from each blocktype.

Setup

As a test site I've started a fresh v9 site with the Portfolio starter kit package.

The site is not that important, it is just to have some starter content.

Next we create a new indexer and hook it up in the startup.cs class:

BlocklistIndexer.cs:

using Examine;
using System;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using static Umbraco.Cms.Core.Constants;

namespace BlocklistIndexing.Indexing
{
    public class BlocklistIndexer : INotificationHandler<UmbracoApplicationStartingNotification>
    {
        private readonly IExamineManager _examineManager;

        public BlocklistIndexer(IExamineManager examineManager)
        {
            _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager));
        }

        public void Handle(UmbracoApplicationStartingNotification notification)
        {
            if (!_examineManager.TryGetIndex(UmbracoIndexes.ExternalIndexName, out IIndex index))
                throw new InvalidOperationException($"No index found by name {UmbracoIndexes.ExternalIndexName}");

            if (index is not BaseIndexProvider indexProvider)
                throw new InvalidOperationException("Could not cast");

            indexProvider.TransformingIndexValues += IndexProvider_TransformingIndexValues;

        }

        private void IndexProvider_TransformingIndexValues(object sender, IndexingItemEventArgs e)
        {
            // TODO
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

startup.cs:

services.AddUmbraco(_env, _config)
    .AddBackOffice()
    .AddWebsite()
    .AddComposers()
    .AddNotificationHandler<UmbracoApplicationStartingNotification, BlocklistIndexer>()
    .Build();
Enter fullscreen mode Exit fullscreen mode

This adds a new BlockListIndexer which runs whenever one or more fields in the index is updated. We also hook it up to the configure services in startup.cs on the ApplicationStarting notification so it's added on the site when it starts up.

Add blocklist indexing logic

Next step is to add some steps where we check if the node being indexed has a blocklist property, if it does we get the blocklist and pass it on to a helper function to extract all searchable text then save that into a new search field.

private void IndexProvider_TransformingIndexValues(object sender, IndexingItemEventArgs e)
{
    if (!int.TryParse(e.ValueSet.Id, out int id)) return;

    using var context = _umbracoContextFactory.EnsureUmbracoContext();
    var content = context.UmbracoContext.Content.GetById(id);

    // Exit if content can't be found or doesn't have the blocklist property
    if (content == null || !content.HasProperty("mainContent")) return;

    var blockList = content.Value<BlockListModel>("mainContent");

    var searchableText = SearchableText(blockList);
    if (string.IsNullOrWhiteSpace(searchableText)) return;

    e.ValueSet.Add("blockListSearch", searchableText);
}
Enter fullscreen mode Exit fullscreen mode

Finally we add the helper function, in this we loop through all the blocks in the blocklist, check their types (based on the generated modelsbuilder models), then we add different properties or even nested blocks to one text string that we in the end add to the field:

private string SearchableText(BlockListModel blocklist)
{
    var searchableText = new List<string>();

    foreach (var block in blocklist)
    {
        if(block.Content is CallToAction)
        {
            var typedBlock = block.Content as CallToAction;
            searchableText.Add(typedBlock.Text);
        }

        if(block.Content is CardsRow)
        {
            var typedBlock = block.Content as CardsRow;
            searchableText.Add(typedBlock.Title);

            // This block has a blocklist with blocks of the type IconCard below, we recurse through it
            var blocks = typedBlock.Cards;
            searchableText.Add(SearchableText(blocks));
        }

        if(block.Content is ProjectPreview)
        {
            var typedBlock = block.Content as ProjectPreview;
            searchableText.Add(typedBlock.Title);
            searchableText.Add(typedBlock.Description);
            searchableText.Add(typedBlock.Image.Name);
        }

        if(block.Content is IconCard)
        {
            var typedBlock = block.Content as IconCard;
            searchableText.Add(typedBlock.Title);
            searchableText.Add(typedBlock.Description);
        }
    }

    return string.Join(" ", searchableText.ToArray());
}
Enter fullscreen mode Exit fullscreen mode

Comparing search fields

That's all it takes. At this point we can rebuild the index, and now we can compare the auto generated search field with our new one:

Auto generated one:

{
    "layout": {
        "Umbraco.BlockList": [
            {
                "contentUdi": "umb://element/649184f94d1340ce959c0cefcd005acd",
                "settingsUdi": "umb://element/1dc5f7680cb84520a2e7145a1c349be4"
            },
            {
                "contentUdi": "umb://element/7a2243381cb74d56a53a370c22e18731",
                "settingsUdi": "umb://element/915b74c12c564a228ff31e716119181e"
            },
            {
                "contentUdi": "umb://element/8ee0bd6662d24961b0882f1da940b50e",
                "settingsUdi": "umb://element/f31f872400324fb4bb266a4356c395a6"
            }
        ]
    },
    "contentData": [
        {
            "contentTypeKey": "0ff0a31a-3727-4cba-b45b-4ba8519499bd",
            "udi": "umb://element/7a2243381cb74d56a53a370c22e18731",
            "title": "Special Skills",
            "cards": {
                "layout": {
                    "Umbraco.BlockList": [
                        {
                            "contentUdi": "umb://element/ac777ec30f5446acb4c2cf3ba7955818",
                            "settingsUdi": "umb://element/4e7e112c781549c2b89dda81c51334d6"
                        },
                        {
                            "contentUdi": "umb://element/81b42c2e8f39415b8b1c6aa656c45e0f",
                            "settingsUdi": "umb://element/4ccd9e7eb32d46dab99670c6bce28b5d"
                        },
                        {
                            "contentUdi": "umb://element/0d402062e033403788f1498d13f1d625",
                            "settingsUdi": "umb://element/002ad580c3f940a29890f17204727d57"
                        }
                    ]
                },
                "contentData": [
                    {
                        "contentTypeKey": "3f6a6761-efa8-426e-9b4b-344c596f8aae",
                        "udi": "umb://element/ac777ec30f5446acb4c2cf3ba7955818",
                        "iconClass": [
                            "Ios Star Outline"
                        ],
                        "title": "Umbraco Master",
                        "description": "I am an Umbraco Certified Master. I love everything about Umbraco, especially v9"
                    },
                    {
                        "contentTypeKey": "3f6a6761-efa8-426e-9b4b-344c596f8aae",
                        "udi": "umb://element/81b42c2e8f39415b8b1c6aa656c45e0f",
                        "iconClass": [
                            "Social Github Outline"
                        ],
                        "title": "Git Expert",
                        "description": "I am an expert at Git. Talk to me if you need some help with rebasing or merging with confilicts."
                    },
                    {
                        "contentTypeKey": "3f6a6761-efa8-426e-9b4b-344c596f8aae",
                        "udi": "umb://element/0d402062e033403788f1498d13f1d625",
                        "iconClass": [
                            "Trophy"
                        ],
                        "title": "Umbraco MVP",
                        "description": "I have been awarded an Umbraco MVP award for my contributions to the Umbraco Community"
                    }
                ],
                "settingsData": [
                    {
                        "contentTypeKey": "0c9bd42b-0bd4-4b98-a899-95d27494e348",
                        "udi": "umb://element/4e7e112c781549c2b89dda81c51334d6",
                        "hide": ""
                    },
                    {
                        "contentTypeKey": "0c9bd42b-0bd4-4b98-a899-95d27494e348",
                        "udi": "umb://element/4ccd9e7eb32d46dab99670c6bce28b5d",
                        "hide": ""
                    },
                    {
                        "contentTypeKey": "0c9bd42b-0bd4-4b98-a899-95d27494e348",
                        "udi": "umb://element/002ad580c3f940a29890f17204727d57",
                        "hide": ""
                    }
                ]
            }
        },
        {
            "contentTypeKey": "8728e9e8-749f-4719-a3f3-6ed780b42141",
            "udi": "umb://element/649184f94d1340ce959c0cefcd005acd",
            "text": "Like what you see?",
            "link": [
                {
                    "name": "Hire me",
                    "url": "/contact/"
                }
            ]
        },
        {
            "contentTypeKey": "96dd9b3c-04ae-47fb-b610-d546b05740bb",
            "udi": "umb://element/8ee0bd6662d24961b0882f1da940b50e",
            "title": "Online Blogging Site",
            "description": "I was fortunate enough to be involved in building the popular blog called codeshare.co.uk. I even migrated it from Umbraco 7 to Umbraco 8.",
            "image": [
                {
                    "key": "e5d880bc-55f4-4201-9373-843372bb39ec",
                    "mediaKey": "121e4357-98a2-4d70-ac44-d87e1732f2e4",
                    "crops": [],
                    "focalPoint": {
                        "left": 0.5,
                        "top": 0.5
                    }
                }
            ]
        }
    ],
    "settingsData": [
        {
            "contentTypeKey": "e2ec53a3-602d-4134-a56e-7ff7fe92a587",
            "udi": "umb://element/915b74c12c564a228ff31e716119181e",
            "itemName": "",
            "hide": "0"
        },
        {
            "contentTypeKey": "e1d2ae73-a9e2-42f5-85ac-c2a1d4c27c3d",
            "udi": "umb://element/1dc5f7680cb84520a2e7145a1c349be4",
            "itemName": "",
            "hide": "0"
        },
        {
            "contentTypeKey": "40c5160c-386f-42cf-9101-b4c816fc8a2d",
            "udi": "umb://element/f31f872400324fb4bb266a4356c395a6",
            "itemName": "",
            "hide": "0"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Our new field:

Like what you see? Special Skills Umbraco Master I am an Umbraco Certified Master. 
I love everything about Umbraco, especially v9 Git Expert I am an expert at Git. 
Talk to me if you need some help with rebasing or merging with confilicts. 
Umbraco MVP I have been awarded an Umbraco MVP award for my contributions to the Umbraco Community Online Blogging Site 
I was fortunate enough to be involved in building the popular blog called codeshare.co.uk. 
I even migrated it from Umbraco 7 to Umbraco 8. 
Codeshare
Enter fullscreen mode Exit fullscreen mode

That's all it takes!

💖 💪 🙅 🚩
jemayn
Jesper Mayntzhusen

Posted on March 14, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024

Modern C++ for LeetCode 🧑‍💻🚀
leetcode Modern C++ for LeetCode 🧑‍💻🚀

November 29, 2024