HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Convert from IContent to ContentApiModel

This topic describes how to customize conversion from IContent to ContentApiModel.

This topic describes how to customize conversion from IContent to ContentApiModel. The following phases simplify the process.

  • Convert from IContent to ContentApiModel.
  • Filter data before and after the conversion. See how to customize data returned to clients.
  • Serialize ContentApiModel and returns data to clients.

The conversion is responsible for converting PropertyData to PropertyModel. The following image shows the flow.

Several important interfaces and classes appear in the flow image.

  • IContentConverterProvider resolves the correct IContentConverter responsible for converting IContent to ContentApiModel. The default implementation of IContentConvert is DefaultContentConverter.
  • IPropertyConverterProvider resolves the correct IPropertyConverter that takes responsibility to convert a PropertyData to a PropertyModel.

🚧

Caution

Developers can use the classes mentioned above, but only if you really need to because of being an internal namespace or marked as preview.

Several scenarios regarding conversion that developers may face are as follows:

  1. Developers would replace a default property model for property data with their model.
  2. Developers create a property data type and need to associate it with a property model.
  3. Developers need to customize Optimizely's conversion logic.

(1) and (2) are much safer than (3), and the following examples show how to handle those scenarios.

🚧

Caution

(3) is complicated and risky so do this only in urgent cases. It is not recommended. Developers can create a new implementation of IContentConverterProvider with a SortOrder higher than 100 and then, implement a new IContentConverter to handle conversion.

Example: customize PropertyDateTime converter

Given that a site requires a DateTime in Utc format, and you decide to create a property data type CustomPropertyDate to handle this, do the following steps:

  • Create a CustomPropertyDateTime
[Serializable]
[PropertyDefinitionTypePlugIn]
public class CustomPropertyDateTime : PropertyDate
{
    // Empty constructor should be present, otherwise it will cause exception during startup.
    public CustomPropertyDateTime()
    {
      // we should use Field, but for demonstration purpose, we use Date property
      Date = DateTime.MinValue;
    }

    public CustomPropertyDateTime(DateTime dateTime)
    {
      Date = dateTime.ToUniversalTime();
    }

    public override object Value
    {
      get => Date;
      set
      {
        switch (value)
        {
          case string s:
            Date = DateTime.Parse(s, CultureInfo.CurrentCulture).ToUniversalTime();
            Modified();
            return;
          case DateTime d:
            Date = d.ToUniversalTime();
            Modified();
            return;
            default:
            throw new NotSupportedException("Cannot parse date time format.");
        }
      }
    }

    public override PropertyDataType Type => PropertyDataType.Date;
    public override Type PropertyValueType => typeof(DateTime);
}
  • Create a CustomDateTimePropertyModel
public class CustomDateTimePropertyModel : PropertyModel<DateTime?, CustomPropertyDateTime>
  {
    public CustomDateTimePropertyModel(CustomPropertyDateTime utcDateTime) : base(utcDateTime)
    {
      Value = (DateTime)utcDateTime.Value;
    }
  }
  • Create a CustomPropertyConverter
public class CustomPropertyConverter : IPropertyConverter
{
  public IPropertyModel Convert(PropertyData propertyData, ConverterContext contentMappingContext)
  {
    return new CustomDateTimePropertyModel(new CustomPropertyDateTime((propertyData as PropertyDate).Date.Value));
  }
}
  • Create CustomPropertyConverterProvider
public class CustomPropertyConverterProvider : IPropertyConverterProvider
{
  public int SortOrder => 200;

  public IPropertyConverter Resolve(PropertyData propertyData)
  {
    if (propertyData is PropertyDate || propertyData is CustomPropertyDateTime)
    {
      return new CustomPropertyConverter();
    }

    return null;
  }
}
  • Register at Startup in ConfigureServices
services.AddSingleton<IPropertyConverterProvider, CustomPropertyConverterProvider>();
services.AddSingleton<IPropertyConverter, CustomPropertyConverter>();

📘

Note

A provider pattern is applied so that if you set SortOrder of another provider higher than Optimizely's, it would be used first. But if another provider does not handle the case (that is, return null as result), then Optimizely providers are used.

Example: expand multiple levels of nested ContentArea property

By default, Content Delivery API only expands one level in the response. So if you have a nested ContentArea property (such as a ContentArea property that contains a block that contains its own ContentArea property), Content Delivery API does not expand the nested ContentArea property. In some scenarios, you might want to return the nested ContentArea in one request, which you can do by creating a ContentArea property converter.

[ServiceConfiguration(typeof (IPropertyConverter), Lifecycle = ServiceInstanceScope.Singleton)]
public class CustomContentAreaPropertyConverter: IPropertyConverter {
  public IPropertyModel Convert(PropertyData propertyData, ConverterContext converterContext) {
    if (propertyData is not PropertyContentArea propertyContentArea) {
      throw new InvalidOperationException();
    }

    var model = new ExpandedContentAreaPropertyModel(propertyContentArea, converterContext, ServiceLocator.Current.GetInstance<IContentExpander>());
    model.Expand(converterContext.Language);

    return model;
  }
}
[ServiceConfiguration(typeof (IPropertyConverterProvider), Lifecycle = ServiceInstanceScope.Singleton)]
public class CustomPropertyConverterProvider: IPropertyConverterProvider {
  public int SortOrder => 200;

  public IPropertyConverter Resolve(PropertyData propertyData) {
    if (propertyData is PropertyContentArea) {
      return new CustomContentAreaPropertyConverter();
    }

    return null;
  }
}
public class ExpandedContentAreaPropertyModel: ContentAreaPropertyModel {
  private readonly IContentExpander _contentExpander;

  public ExpandedContentAreaPropertyModel(PropertyContentArea propertyContentArea, ConverterContext converterContext, IContentExpander contentExpander): base(propertyContentArea, converterContext) {
    _contentExpander = contentExpander;
  }

  /// <summary>
  /// Herre we override Expand behaviour of this model and expands all level
  /// </summary>
  /// <param name="language"></param>
  /// <returns></returns>
  protected override IEnumerable<ContentApiModel> ExtractExpandedValue(CultureInfo language) {
    var expandedValue = new List<ContentApiModel>();
    foreach(var item in Value) {
      var expandedModel = _contentExpander.Expand(item.ContentLink, CreateExpandableContext(item.ContentLink, ConverterContext, language));

      expandedValue.Add(expandedModel);
    }

    return expandedValue;
  }

  private static ConverterContext CreateExpandableContext(ContentModelReference contentLink, ConverterContext converterContext, CultureInfo language) => new(new ContentReference(contentLink.Id.Value, contentLink.WorkId.Value), language, converterContext.Options, converterContext.ContextMode, null, "*", false);
}