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
toContentApiModel
. - 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 correctIContentConverter
responsible for convertingIContent
toContentApiModel
. The default implementation ofIContentConvert
isDefaultContentConverter
.IPropertyConverterProvider
resolves the correctIPropertyConverter
that takes responsibility to convert aPropertyData
to aPropertyModel
.
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:
- Developers would replace a default property model for property data with their model.
- Developers create a property data type and need to associate it with a property model.
- 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 aSortOrder
higher than 100 and then, implement a newIContentConverter
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);
}
Updated 6 months ago