Optimizely CMS 12: working with Enums and Content Delivery API

We’re currently setting up an application that utilizes a headless CMS, allowing us to decouple the front-end application from the CMS core.

In a headless setup, having a simple contract between the backend and frontend has a significant impact on the developer experience. And being able to use enum is handy in C# content type models.

How enums behave by default

Enumerable types, by default, appear to behave just as integers. Here’s a minimal example:

public enum Color { Red, Green, Blue }

[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    public virtual Color LogoColor { get; set; }
}
Code language: C# (cs)

Given the above content type definition, the property will render as a number input, giving user no hint about the allowed range of options and their meaning:

Similarly, the response from the Content Delivery REST API contains the value represented as an integer. So the API users receive incomplete information on which enum option is selected, unless they have coded the meaning of integers on their side too:

Improvement 1: display a dropdown with enum values in the CMS Admin Panel

The first improvement we can make is to display a dropdown with enum values instead of a raw number input:

To achieve that, we need a custom selection factory. For example, this implementation is pretty generic and reusable:


[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Color>))]
    public virtual Color LogoColor { get; set; }
}

public class EnumSelectionFactory<TEnum> : ISelectionFactory where TEnum : Enum
{
    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata) => Enum
        .GetValues(typeof(TEnum))
        .Cast<object>()
        .Select(value => new SelectItem()
        {
            Text = value.ToString(),
            Value = (int)value
        });
}
Code language: C# (cs)

This change improves the experience for content editors, but the value is still returned as PropertyNumber by the Content Delivery API.

Improvement 2A: change property type from enum to string

One tradeoff we might find acceptable in some instances is to change the backing type to a string:

[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Color>))]
    public virtual Color LogoColor { get; set; }

    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactoryString<Color>))]
    public virtual string LogoColorAsString { get; set; }
}

public class EnumSelectionFactoryStringValue<TEnum> : ISelectionFactory where TEnum : Enum {
    // ...
        Value = value.ToString()
    // ...
}
Code language: C# (cs)

In this situation, the property will still render as a dropdown. We therefore keep the convenience of constraining the value to enum values.

However, the API will now return the string value, which might be preferred:

The additional benefit is that we can use the Nullable<string> type and easily code the dropdown to represent the “no selection” value. I observed that this is not possible with enums. Property types like Color and Color? are represented the same in CMS’s database. The attempt to set a null value to Color? leads to an error during content type save.

Improvement 2B: keep the enum as enum, but add annotations to swagger.json

In my task, I wanted to keep the type as an enum to guarantee compatibility during export and import operations.

For this reason, I resorted to another trick. I updated the project’s Swagger (OpenAPI) specification to expose names and descriptions for numeric enum values using the x-enum-varnames extension.

While the Content Delivery API still returns integers, the TypeScript client code generator we use (OpenApi Generator) now can map those integer values to TypeScript enum-like types.

Exposing Swagger for Optimizely CMS Content Delivery API is challenging with CMS 12, and I cannot provide a concise code example, but it can be done. Hopefully, this hint can help you see that as a viable option.

Also, a bad idea to avoid: use ContentConverter to convert enum values to string before generating API response

I’ll mention that before I settled on the solution described above, I tried one more, and it turned out to be a disaster.

I wanted to extend and override the DefaultContentConverter so that I can transform the API response just before it’s sent back to the client, and replace integer values with strings.

The concept of the conversion extension I wanted to have.

This was a dead end I spent many hours on, and I advise not trying it.

Firstly, as the linked documentation states, this is an internal API and will likely change in the future.

Secondly, coding this requires traversing the content tree returned by the API and finding enums, and it cannot be done without the use of Reflection APIs, the infamous dynamic type, and falling into a lot of pitfalls. I believe you could make it work “in most cases”, in hundreds of ugly lines of code, but it’s not worth it. Check out the easier options first.

Good luck!

No comments yet, you can leave the first one!

Leave a Comment