Optimizely Content Graph - Sync Computed Getter Properties like with Search & Navigation

Posted on October 18, 2024

Recently, I have been re-writing my blog using Optimizely CMS. During this process, I wanted to try new released technologies offered by Optimizely. Today, I'm going to talk a bit about my experience with Content Graph.

Of course, I thought about using Search & Navigation, but I think it was beneficial that I learned something that will eventually, to my opinion, replace it entirely.

With Search & Navigation, the majority of Optimizely developers are well aware that you can synchronize not only properties meant for the content type itself, but also the ones that are computed, meaning only a getter has been used. The following example demonstrate that (see FirstPublishedDate):

Figure 1

If you use Search & Navigation, this property is getting indexed and can be used on any query. It also means you can order your result with that computed field and do a bunch of cool tricks, such as including different ways to represent a certain element from the content type in a fashion consumable by the Search & Navigation C# client. Complex types aren’t really its force, so we often add getter only properties to return a more simplified, but useful, data format.

So, under Content Graph, I was under the impression that I could do the same thing, but alas, no, you can't for the moment. But Optimizely did confirmed to myself via a support request they have plans to add something that would allow developers to customize and add the ability to extend the data synchronization of content types. In the meantime, I have concocted a very... but very hackish workaround that allow you to synchronize getter properties. Before moving forward with the solution, a little explanation is in order to understand a bit more what you can/can't do:

The schedule job "Optimizely Graph content synchronization job" is the main responsible to do the following:

  • Scan for all existing content types and synchronize them to Content Graph. It's using their definitions to know the structure your content should have under its own engine.
  • Then scans for all existing content in the CMS and synchronize all of its data under Content Graph. This is the step that interests us

While digging for a solution to extend my content type and allow the synchronization of getter only properties, I have realized the following must be respected:

  • Your property cannot be a getter only, you still have to put a setter. It's because otherwise the property is not considered a real CMS property and thus is getting ignored during the indexation. A real property will appear under "CMS -> Settings -> Content Type" menu.
  • So I simply added a setter using this.SetPropertyValue() to get over this limitation.

Once you get your property synchronized to Optimizely database comes the part where the indexation job of Content Graph is still ignoring it. While reading the decompiled code of Optimizely, I learned that the current business logic is only relying on the data stored within the CMS database, which means that in the end all defined elements in the code is entirely ignored, since the job is only directly pulling the data inside the internal content type tables.

So, well, this is where I come with the idea to build a dynamic type. I had to create a dynamic assembly which was allowed to interact with "internal" types of the "Optimizely.ContentGraph.Cms.NetCore" assembly. In the end, I had to create IL code so that my desired behavior could be added to the synchronization job. To keep that brief, if you want the solution, here's my GitHub repository with a small example. The dynamic type is inheriting from "ContentGraphContentConverter" and is replacing the original type under the IoC container. Under the "Convert" method, instead of directly returning the model, I'm calling the extension AddReadonlyProperties, which then allow me to customize the return value.

Happy coding!