Loading...

Last updated: Oct 30 2018

Area: Content Delivery API Applies to versions: 2 and higher

How to customize API to change data returned to clients

This topic describes how to customize the Content Delivery API to change data returned to clients. There are three main sections in this article

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

 [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;
	}
}

Do you have feedback on this documentation? Send an email to documentation@episerver.com. For development-related questions and discussions, refer to our Forums on https://world.episerver.com/forum/