Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more

Area: Optimizely Content Delivery API
Applies to versions: 2.6 and lower
Other versions:
ARCHIVED This content is retired and no longer maintained. See the version selector for other versions of this topic.

How to customize API to change data returned to clients

This topic describes how to customize the Optimizely Content Delivery API to change data returned to clients.

Note: This documentation only applies to version 2.6.1 and lower of the API. We are currently working on updating it for the latest version.

ContentResultService

This service takes responsibility to handle data before returning it to clients. Here, this class is customized so that it can limit the payload returned to clients. With this customized class, only fields needed by clients can be returned through the API.

  1. Create a new class, CustomContentResultService, and decorate it with ServiceConfiguration(typeof(ContentResultService)).
  2. Register this new class as the default instance for ContentResultService in InitializationModule.
  3. Endpoint sample should be http://localhost:8080/api/episerver/v2.0/content/12?fields=contentLink,name,language,productPageLinks,relatedContentArea,mainContentArea
    Code example from Alloy template site:

 [ServiceConfiguration(typeof(ContentResultService))]
 public class CustomContentResultService : ContentResultService
   {
     public CustomContentResultService(IContentApiSerializer contentApiSerializer) : base(contentApiSerializer)
       {
       }
     /// <summary>
     /// Build string content from object use given serializer
     /// (1) Only return needed fields to clients (2) Only applied for content api not search api
     /// </summary>
     public override StringContent BuildContent(object value)
       {
         var fields = System.Web.HttpContext.Current.Request.Params["fields"];
         if (string.IsNullOrEmpty(fields) || !(value is ContentApiModel))
           {
             return base.BuildContent(value);
           }
         var returnedProperties = fields.Split(',');
         var convertedObj = new ExpandoObject() as IDictionary<string, Object>;
         Func<string[], string, bool> shouldIncludeProperty = (propertyList, property) =>
           {
             return propertyList.Any(prop => string.Equals(prop, property, StringComparison.InvariantCultureIgnoreCase));
           };
         foreach (var prop in value.GetType().GetProperties())
           {
             var propertyType = prop.PropertyType;
             if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
               {
                 var propertyDataDict = prop.GetValue(value, null);
                 foreach (var item in propertyDataDict as IDictionary<string, object>)
                   {
                     if (!shouldIncludeProperty(returnedProperties, item.Key))
                       {
                         continue;
                       }
                     convertedObj.Add(item.Key, item.Value);
                   }
                 continue;
               }
             if (!shouldIncludeProperty(returnedProperties, prop.Name))
               {
                 continue;
               }
             var propValues = prop.GetValue(value, null);
             convertedObj.Add(prop.Name, propValues);
           }
         return base.BuildContent(convertedObj);
       }
  }
[InitializableModule]
public class DependencyResolverInitialization : IConfigurableModule
  {
    public void ConfigureContainer(ServiceConfigurationContext context)
      {
        //Implementations for custom interfaces can be registered here.
        context.ConfigurationComplete += (o, e) =>
          {
            //Register custom implementations that should be used in favour of the default implementations
            context.Services.AddTransient<IContentRenderer, ErrorHandlingContentRenderer>()
            .AddTransient<ContentAreaRenderer, AlloyContentAreaRenderer>()
            .AddTransient<ContentResultService, CustomContentResultService>();
          };
      }
    public void Initialize(InitializationEngine context)
      {
        DependencyResolver.SetResolver(new ServiceLocatorDependencyResolver(context.Locate.Advanced));
      }
    public void Uninitialize(InitializationEngine context)
      {
      }
    public void Preload(string[] parameters)
      {
      }
  }

ContentAreaPropertyModel

By default, the Content Delivery API only expands content at one level in depth. In this section, ContentAreaPropertyModel is customized so that it can expand all property levels in ContentArea.

  1. Create a new class, CustomContentAreaPropertyModel, which inherits from ContentAreaPropertyModel and overrides the ExtractExpandedValue() method. Basically, this calls _contentModelMapper.TransformContent with the expand parameter Is set to "*". (The default value is empty which is why only one property level is expanded to reduce the response payload.)
  2. Create a new class, CustomContentAreaPropertyConverter, which inherits from DefaultPropertyModelConverter and register the newly created CustomContentAreaPropertyModel.
/// <summary>
/// Custom view model to mapped with <see cref="PropertyContentArea"/>
/// </summary>
public class CustomContentAreaPropertyModel : ContentAreaPropertyModel
  {
    public CustomContentAreaPropertyModel(PropertyContentArea propertyContentArea,bool excludePersonalizedContent) 
      : base(propertyContentArea, excludePersonalizedContent)
      {      
      }
    /// <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>();
        var contentReferences = Value.Where(x => x.ContentLink != null).Select(x => new ContentReference(x.ContentLink.Id.Value));
        var content = _contentLoaderService.GetItems(contentReferences, language).ToList();
        var principal = ExcludePersonalizedContent ? _principalAccessor.GetAnonymousPrincipal() : _principalAccessor.GetCurrentPrincipal();
        var filteredContent = content.Where(x => _accessEvaluator.HasAccess(x, principal, AccessLevel.Read)).ToList();
        filteredContent.ForEach(x => expandedValue.Add(_contentModelMapper.TransformContent(x, ExcludePersonalizedContent, "*")));
        return expandedValue;
      }
  }
/// <summary>
/// Custom converter for <see cref="PropertyContentArea" /> so Headless will use our custom custom view model when convert this property/>
/// </summary>
[ServiceConfiguration(typeof(IPropertyModelConverter), Lifecycle = ServiceInstanceScope.Singleton)]
public class CustomContentAreaPropertyConverter : DefaultPropertyModelConverter
  {
    /// <summary>
    /// The Default converter of Headless has SortOrder = 0, so our custom converter should have this property with value greater than 0
    /// so Headless will prioritize the custom converter over the default converter.
    /// </summary>
    public override int SortOrder { get; } = 100;
    protected override IEnumerable<TypeModel> InitializeModelTypes()
      {
        var typeList = new List<TypeModel>();
        typeList.Add(new TypeModel() 
          { 
            PropertyType = typeof(PropertyContentArea),
            ModelType = typeof(CustomContentAreaPropertyModel),
            ModelTypeString = typeof(CustomContentAreaPropertyModel).FullName 
          });
        return typeList;
      }
  }

Related topics

Last updated: Jun 24, 2021