Content Hub Client .NET Client Built With Web SDK and Wrapped in Azure Function

SergeyYatsenko
Sitecore Technology MVP & Sr. Director
  • Twitter
  • LinkedIn

Content Hub Client .NET Client Built With Web SDK and Wrapped in Azure Function

Overview

November 3rd, 2021

This post is a quick walkthrough on how to read Entity fields and relation data from Content Hub. One way to do this is to use Content Hub REST API, and another is to leverage StyleLabs Web SDK, a .NET library which provides simple abstractions on top of that REST API. Below are the details on my Azure Function, which reads Content Hub Entity fields and its relations, hope this will be useful for those who need to get started on this.

Overview

This post is a quick walkthrough on how to read Entity fields and relation data from Content Hub. One way to do this is to use Content Hub REST API, and another is to leverage StyleLabs Web SDK, a .NET library that provides simple abstractions on top of that REST API. Below are the details on my Azure Function, which reads Content Hub Entity fields and its relations, hope this will be useful for those who need to get started on this.

Before we move on to the code part, I must mention that it depends on the OAuth client, which needs to exist in the Content Hub - here are the steps for creating an OAuth client.

GetEntityData Azure Function

The below function payload with target Entity ID along with the relation names and OAuth credentials. Relation names specify which related entities need to be read and appended to function output. The successful result of this function will be a payload consisting of all Entity properties, collection of related entities and their entity properties, and finally a list of renditions of the target Entity. Here's how it will look like

{
    "Properties": { /* target entity properties */},
    "Relations": { /* a collection of elements, holding properties of the related entities, specified in EntityRelations parameter above */},
    "Renditions": { /* collection of rendition names and their Urls in Content Hub */}
}

Without further ado let's move to the function code, to which I added multiple comments to make it a bit easier to understand

//#r "Newtonsoft.Json"

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json;
using Stylelabs.M.Framework.Essentials.LoadConfigurations;
using Stylelabs.M.Sdk.Contracts.Base;
using Stylelabs.M.Sdk.WebClient;
using Stylelabs.M.Sdk.WebClient.Authentication;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace SY.ContentHub.AzureFunctions
{
    /// 
    /// Read an Entity and some of its relations (as per request parameters) 
    /// and return it all in one an easy to consume JSON, which will look like so:
    /// {
    ///	    "Properties": { /* target entity properties */},*
    ///     "Relations": { /* a collection of elements, holding properties of the related entities, specified in EntityRelations parameter above */},*
    ///     "Renditions": { /* collection of rendition names and their Urls in Content Hub */}
    /// }
    /// Feel free to remove all this extensive logging, update and reuse it as needed!
    /// 
    public static class GetEntityData
    {
        [FunctionName("GetEntityData")]
        public static async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestMessage req, TraceWriter log)
        {
            log.Info("Invoked GetEntityData function...");
            //Read request payload and extract target entity ID from it.
            //I coded it this way for payload to be flexible, so this function can be reused in many scenarios,
            //including direct call from the Content Hub trigger - as long as 
            var content = req.Content;
            string requestBody = content.ReadAsStringAsync().Result;
            log.Info($"Request body: {requestBody}");
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            var targetEntityIdJsonPath = Utils.GetHeaderValue(req.Headers, "TargetEntityIdJsonPath");
            //EntityRelations is a '|'-separated list of relation names for the related entities that need to be included in response
            var entityRelations = Utils.GetHeaderValue(req.Headers, "EntityRelations").Split("|");
            log.Info($"Json paths: targetEntityIdJsonPath-{targetEntityIdJsonPath}, entityRelations-{entityRelations}");
            long targetIdValue = (long)data.SelectToken(targetEntityIdJsonPath);
            log.Info($"targetIdValue: {targetIdValue}");

            //Url of your Content Hub hist
            Uri endpoint = new Uri(Utils.GetHeaderValue(req.Headers, "ContentHubUrl"));
            //OAuth client name and credentials
            OAuthPasswordGrant oauth = new OAuthPasswordGrant
            {
                ClientId = Utils.GetHeaderValue(req.Headers, "ClientId"),
                ClientSecret = Utils.GetHeaderValue(req.Headers, "ClientSecret"),
                UserName = Utils.GetHeaderValue(req.Headers, "UserName"),
                Password = Utils.GetHeaderValue(req.Headers, "Password")
            };

            // Create the Web SDK client
            IWebMClient client = MClientFactory.CreateMClient(endpoint, oauth);
            IEntity entity = await client.Entities.GetAsync(targetIdValue, EntityLoadConfiguration.Full);
            log.Info("Made request to CH...");
            if (entity == null)
            {
                //Return 404 for when Entity with target ID was not found
                return new HttpResponseMessage(HttpStatusCode.NotFound)
                {
                    Content = new StringContent($"{{\"targetEntityId\":{targetIdValue}}}")
                };
            }

            //Iterate through relation names and retrieve the related entities - add them to 
            var relations = new Dictionary>();
            foreach (var entityRelation in entityRelations)
            {
                if (!string.IsNullOrEmpty(entityRelation))
                {
                    //Read entity relation and get the IDs of related entities
                    var relation = entity.GetRelation(entityRelation);
                    var relatedIds = relation?.GetIds();
                    if (relatedIds.Count > 0)
                    {
                        var relationData = new List();
                        foreach (var relatedId in relatedIds)
                        {
                            //Read related entity data (ignore if not found)
                            IEntity relatedEntity = await client.Entities.GetAsync(relatedId, EntityLoadConfiguration.Full);
                            if (relatedEntity != null)
                            {
                                var entityData = new
                                { 
                                    Properties = ExtractEntityData(relatedEntity),
                                    Renditions = entity.Renditions
                                };
                                relationData.Add(entityData);
                            }
                        }
                        if (relationData.Count > 0)
                        {
                            relations.Add(entityRelation, relationData);
                        }
                    }
                }
            }
            
            //Create a dynamic object, then serialize it to output JSON
            var result = new
            {
                Properties = ExtractEntityData(entity),
                Renditions = entity.Renditions,
                Relations = relations
            };
            var resultJson = JsonConvert.SerializeObject(result);
            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(resultJson, Encoding.UTF8, "application/json")
            };
        }

        /// 
        /// Helper method to read Entity properties and save them in Dictionary object
        /// 
        /// 
        /// 
        private static Dictionary ExtractEntityData(IEntity entity)
        {
            var e = new Dictionary();
            //Read some of the main/root entity properties
            e.Add("Id", entity.Id);
            e.Add("Identifier", entity.Identifier);
            e.Add("DefinitionName", entity.DefinitionName);
            e.Add("CreatedBy", entity.CreatedBy);
            e.Add("CreatedOn", entity.CreatedOn);
            e.Add("IsDirty", entity.IsDirty);
            e.Add("IsNew", entity.IsNew);
            e.Add("IsRootTaxonomyItem", entity.IsRootTaxonomyItem);
            e.Add("IsPathRoot", entity.IsPathRoot);
            e.Add("IsSystemOwned", entity.IsSystemOwned);
            e.Add("Version", entity.Version);
            e.Add("Cultures", entity.Cultures);
            //Then read and add all the custom properties
            foreach (var property in entity.Properties)
            {
                try
                {
                    var propertyValue = entity.GetPropertyValue(property.Name);
                    e.Add(property.Name, propertyValue);
                }
                catch (Exception ex) when (ex.Message == "Culture is required for culture sensitive properties.")
                {
                    var propertyValue = entity.GetPropertyValue(property.Name, CultureInfo.GetCultureInfo("en-US"));
                    e.Add(property.Name, propertyValue);
                }
            }
            
            return e;
        }
    }
}



Useful links

Related Blogs

Latest Blogs