K Khan
Jul 2, 2018
visibility 7378
star star star star star star
(5 votes)

Support of IList<T> in EPiServer Content Delivery API

In Content delivery API AKA(Headless API), IPropertyModelHandler contains the list of all property models that should be in included in JSON, including images, Content Areas, and content references etc. But at this stage, our custom types based on PropertyList<CustomType> will not be included in the JSON output, the reason is obvious, as those are custom types. But it is easy to add those in JSON, below example, will walk you through the steps and will show the strength of Content Delivery API also.

Following Code is generally use to create IList<T> properties for pages and Blocks

public class Notes
{
        [Display(Name = "Note 1")]
        public string Note1 { get; set; }
 
        [Display(Name = "Rank", Description = "1 - 10")]
        [Range(1, 10)]
        public int Rank { get; set; }
}

[PropertyDefinitionTypePlugIn]
    public class NotesList : PropertyList<Notes>
    {
        protected override Notes ParseItem(string value)
        {
            return JsonConvert.DeserializeObject<Notes>(value);
        }

        public override PropertyData ParseToObject(string value)
        {
            ParseToSelf(value);
            return this;
        }
    }

//Use it in your page/block

[CultureSpecific]
        [Display(Name = "Notes", Description = "Notes", Order = 10)]
        [EditorDescriptor(EditorDescriptorType = typeof(CollectionEditorDescriptor<Notes>))]
        public virtual IList<Notes> PageNotes { get; set; }

Create a new type model 

public class NotesPropertyModel : PropertyModel<IList<Notes>, NotesList>
    {
        public NotesPropertyModel(NotesList type) : base(type)
        {
            base.Value = type.List;
        }
    }
}

Register that type model in an initialization module.

[InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class CustomPropertyModelIntialization : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            var propertyModelHandler = ServiceLocator.Current.GetInstance<IPropertyModelHandler>();
            propertyModelHandler.ModelTypes.Add(new TypeModel
            {
                ModelType = typeof(NotesPropertyModel),
                ModelTypeString = "NotesPropertyModel",
                PropertyType = typeof(NotesList)
            });

        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
        }
    }

Resulted JSON

"pageNotes": {
                    "value": [
                        {
                            "note1": "Happy development",
                            "rank": 5                        },
                        {
                            "node1": "Happy Content Delivery API development",
                            "rank": 5,
                        }
                    ],
                    "propertyDataType": "NotesList"
                }

In case if you haven't explored Content Delivery API yet, follow the trail:

Jul 02, 2018

Comments

Michael Nylund
Michael Nylund Jun 17, 2019 11:22 AM

Hi Khan,
Any idea of how to rewrite this for the updated ContentApi version 2.4.0.0 where IPropertyModelConverter is utilized instead of IPropertyModelHandler?

Regards,
Michael

K Khan
K Khan Jun 17, 2019 11:43 AM

Haven't work my self on V2. But these were the differences between IPropertyModelHandler and IPropertyModelConverter 

  • GetValue method is now ConvertPropertyModel
  • CanHandleProperty method is now HasPropertyModelAssociatedWith
  • ModelTypes property used to return List<TypeModel> but now returns IEnumerable<TypeModel>

Nenad
Nenad Jan 21, 2022 04:45 PM

Does anybody knows how to create this in v3 and CMS 12? 
I tried to follow the latest documentation https://world.optimizely.com/documentation/developer-guides/content-delivery-api/getting-started/serialization/ but non of that works.

Every interface that I tried to use is undefined, like IPropertyModelConverter, IContentModelMapper or DefaultPropertyModelConverter type. Music festival demo project is of on use also (CMS 11, not .CORE).

Minesh Shah (Netcel)
Minesh Shah (Netcel) Jan 28, 2022 10:03 AM

@Nenad we got this working by implementing IPropertyConverter

e.g. 

    /// <summary>
    /// image items property convertor
    /// </summary>
    public class ImageItemListPropertyConvertor : IPropertyConverter
    {
        private readonly IUrlResolver _urlResolver;

        public ImageItemListPropertyConvertor(IUrlResolver urlResolver)
        {
            _urlResolver = urlResolver;
        }

        public IPropertyModel Convert(PropertyData propertyData, ConverterContext contentMappingContext)
        {
            return new ImageItemPropertyModel((PropertyList<ImageItemModel>)propertyData, _urlResolver);
        }
    }
    public class ImageItemPropertyModel : PropertyModel<IEnumerable<ImageItemDto>, PropertyList<ImageItemModel>>
    {
        private readonly IUrlResolver _urlResolver;

        /// <summary>
        /// Initializes a new instance of the <see cref="ImageItemPropertyModel"/> class.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="urlResolver"></param>
        public ImageItemPropertyModel(PropertyList<ImageItemModel> type, IUrlResolver urlResolver)
            : base(type)
        {
            _urlResolver = urlResolver;
            Value = GetValues(type.List);
        }

        private IEnumerable<ImageItemDto> GetValues(IList<ImageItemModel> items)
        {
            if (items == null)
            {
                return Enumerable.Empty<ImageItemDto>();
            }

            var images = new List<ImageItemDto>();

            foreach (var item in items)
            {
                var icon = new ImageItemDto();
                if (!ContentReference.IsNullOrEmpty(item.Image))
                {
                    icon.ImageUrl = _urlResolver.GetUrl(item.Image);
                }

                icon.ImageAltText = item.AltText;

                images.Add(icon);
            }

            return images;
        }
    }

Nenad
Nenad Jan 28, 2022 01:20 PM

Thank you @Minesh for your response. 

With the latest version of the Content Delivery API it's enough to have your custom property model inherits PropertyModel<IList<MyModel>, MyModelList> and it works.  No need for the PoropertyConverter to be implemented. At least in my case.

I had a corrupted DB and that's why nonthing works what ever I tried.

error Please login to comment.
Latest blogs
Add more scheduled job settings from the Optimizely CMS 12 admin UI -- with OptiScheduledJob.ExtraParameters

  Optimizely (EPiServer) CMS 12 ships a great scheduled-jobs framework, but it has one frustrating gap: a job has nowhere to store its own...

Binh Nguyen Thi | Jun 25, 2026

Automated Search & Navigation to Graph Migration with Claude Code

A Claude Code plugin that scans your S&N codebase, applies Graph SDK transformations, and validates the result. Install once, run one command. CMS ...

Connor Fortin | Jun 24, 2026

Migrating from Find to Graph: Lessons Learned from a Real CMS 13 Project

While migrating a search solution from Optimizely Search & Navigation (Find) to Optimizely Graph in CMS 13, I encountered several issues that were...

Binh Nguyen Thi | Jun 24, 2026

Optimizely: Upgrade Opti-ID and .NET 10 in CMS 12

Many Optimizely customers are planning their roadmap around a future migration to Optimizely CMS 13. As a result, upgrades such as Opti ID adoption...

Madhu | Jun 23, 2026 |

Understanding Optimizely Graph: Caching, Webhooks & Avoiding Stale Content (Optimizely SaaS CMS)

📌 Scope: This post covers Optimizely CMS (SaaS) only — using the official @optimizely/cms-sdk and @optimizely/cms-cli packages with Next.js 15. If...

Kiran Patil | Jun 23, 2026 |

Optimizely Content APIs: the Setup the Docs Don't Walk You Through

CMS 13 is pushing things firmly in the direction of Optimizely Graph, but plenty of teams are still running on older CMS versions, or have good...

Andre | Jun 22, 2026