Auto-tagging Assets in Sitecore Content Hub

Introduction

Sitecore Content Hub incorporates a powerful Media Processing functionality. This functionality allows users to perform predefined sets of tasks on various assets when they are created or updated in the system. It serves as an effective mechanism for scenarios where asset metadata needs to be extracted, calculated, or generated using third-party services, such as image analysis and tagging with Microsoft cognitive services.

However, there are cases where a more customized approach is required. In this post, I will discuss a typical scenario in Content Hub implementations where custom, business-specific metadata needs to be associated with assets that have been uploaded to the system from another source.

Use case

In this particular scenario, the original source of the assets was the Sitecore Media Library. Upon uploading these assets, tags and taxonomies needed to be generated based on the media library path. The media library path followed the company's business rules, where specific segments represented divisions, rooms, community names, floor plans, and elevations. These segments needed to be converted into Content Hub tags and taxonomies.

Script, Action, and Trigger

To trigger the following script on asset creation or update, I set up a trigger that calls an action, which in turn executes the script below. The [Context.Target](http://context.target/) in the script refers to the object that represents the entity being created or updated.

Content Hub Script

The code below is pretty straightforward and self-explanatory (or so I think :)). My intention is to share some boilerplate code, which can serve as a good starting point for your implementation.

Happy coding.

using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;

MClient.Logger.Info($"Start Extracting tags for Asset.");

var targetEntity = Context.Target as IEntity;
if (targetEntity == null)
{
    MClient.Logger.Debug($"Error extracting tags for Asset ID: targetEntity is null.");
    return;
}

// Ensure the following members are loaded  
await targetEntity.LoadMembersAsync(new PropertyLoadOption("FileName"), RelationLoadOption.All);  

MClient.Logger.Debug($"Extracting tags for Asset ID: {targetEntity.Id}");

var fileName = targetEntity.GetProperty<ICultureInsensitiveProperty>("FileName");
MClient.Logger.Debug("targetEntity fileName: " + fileName);
var fileNameValue = fileName?.GetValue<string>();
MClient.Logger.Debug($"fileName value: {fileNameValue}");

if(!string.IsNullOrEmpty(fileNameValue))
{
    //Set custom asset type
    var fileExtension = GetFileExtension(fileNameValue);
    MClient.Logger.Debug($"fileExtension: {fileExtension}");
    if(!string.IsNullOrEmpty(fileExtension))
    {
        var imageExtensions = new List<string>{"bmp","bpg","cr","cr2","crw","dng","dpx","erf","jfif","jp2","jpeg","jpg","jxr","nef","orf","raf","raw","srw","tga","webp","ai","eps","png","psb","psd","svg","tif","tiff"};
        if(imageExtensions.Contains(fileExtension))
        {
            MClient.Logger.Debug($"Assigning the MeritageImageAsset AssetType.");
            var result = await AssignAssetType("M.AssetType", "M.AssetType.MeritageImageAsset", targetEntity);
        }
    }

    //Extract tags from filename
    var fileName = GetFileNameWithoutExtension(fileNameValue);
    var tags = fileName.Split('_');
    var divisionName = tags.Length > 1 ? tags[0] : null;
    var communityName = tags.Length >= 2 ? tags[1] : null;
    var floorplanName = tags.Length >= 3 ? tags[2] : null;
    var roomName = tags.Length >= 4 ? tags[3] : null;
    var elevationName = tags.Length >= 5 ? tags[4] : null;
    
    MClient.Logger.Debug($"Tag names. divisionName : {divisionName}, communityName : {communityName}, floorplanName : {floorplanName}, roomName : {roomName}, elevationName : {elevationName},");

    if(!string.IsNullOrEmpty(divisionName))
    {
        var result = await AssignTaxonomy("MH.Division", "TaxonomyName", "AssetsToDivisions", divisionName, targetEntity);
    }

    if(!string.IsNullOrEmpty(communityName))
    {
        var result = await AssignTaxonomy("MH.CommunityName", "TaxonomyName", "AssetsToCommunityNames", communityName, targetEntity);
    }

    if(!string.IsNullOrEmpty(floorplanName))
    {
        var result = await AssignTaxonomy("MH.FloorPlan", "TaxonomyName", "AssetsToFloorPlans", floorplanName, targetEntity);
    }

    if(!string.IsNullOrEmpty(roomName))
    {
        var result = await AssignTaxonomy("MH.Room", "TaxonomyName", "AssetsToRooms", roomName, targetEntity);
    }

    if(!string.IsNullOrEmpty(elevationName))
    {
        var result = await AssignTaxonomy("MH.Elevation", "TaxonomyName", "AssetsToElevations", elevationName, targetEntity);
    }
}

private string GetFileNameWithoutExtension(string fileName)
{
    int fileExtPos = fileName.LastIndexOf(".");
    if (!fileName.StartsWith(".") && fileExtPos > 0 && fileName.Length >= 3)
        return fileName.Substring(0, fileExtPos);
    else
        return fileName;
}

private string GetFileExtension(string fileName)
{
    int index = fileName.LastIndexOf(".");
    MClient.Logger.Debug($"Getting the filename extension for filename {fileName}. last dot index: {index}");
    if (index == -1)
        return "";

    if (index == fileName.Length - 1)
        return "";

    return fileName.Substring(index + 1).ToLower();
}

private async Task<bool> AssignRelation(string relationName, long? relatedEntityId, IEntity asset)
{
    MClient.Logger.Debug($"Assigning relation ID: {relatedEntityId} to Entity ID: {asset?.Id}. Relation Name: {relationName}");
    var assetRelation = asset.GetRelation(relationName);
    if(assetRelation != null)
    {
        var existingRelationIds = assetRelation.GetIds();
        if(!existingRelationIds.Contains(relatedEntityId.Value))
        {
            existingRelationIds.Add(relatedEntityId.Value);
            assetRelation.SetIds(existingRelationIds);
            
            var result = await MClient.Entities.SaveAsync(asset).ConfigureAwait(false);
            return true;
        }
    }

    return false;
}

private async Task<bool> AssignTaxonomy(string definitionName, string propertyName, string relationName, string tagValue, IEntity asset)
{
    if(!string.IsNullOrEmpty(definitionName) && !string.IsNullOrEmpty(relationName) && !string.IsNullOrEmpty(tagValue))
    {
        var query = Query.CreateQuery(entities => from e in entities 
                                    where e.DefinitionName == definitionName 
                                    && e.Property(propertyName) == tagValue.ToLower()                                      
                                    select e);
        
        var relatedEntityId = await MClient.Querying.SingleIdAsync(query);
        if(relatedEntityId != null)
        {
            MClient.Logger.Debug($"Related entity ID: {relatedEntityId}");
            var result = AssignRelation(relationName, relatedEntityId, asset);
            return true;
        }
    }

    return false;

}

private async Task<bool> AssignAssetType(string definitionName, string identifier, IEntity asset)
{
    if(!string.IsNullOrEmpty(definitionName) && !string.IsNullOrEmpty(identifier) && asset != null)
    {
        var query = Query.CreateQuery(entities => from e in entities 
                                    where e.DefinitionName == definitionName 
                                    && e.Identifier == identifier                                      
                                    select e);
        
        var relatedEntityId = await MClient.Querying.SingleIdAsync(query);
        if(relatedEntityId != null)
        {
            MClient.Logger.Debug($"Related entity ID: {relatedEntityId}");

            var assetTypeToAssetRelation = asset.GetRelation<IChildToOneParentRelation>("AssetTypeToAsset");  
            //var assetRelation = asset.GetRelation("AssetTypeToAsset");
            if(assetTypeToAssetRelation != null)
            {
                MClient.Logger.Debug($"Assigning assetTypeToAssetRelation");
                //var result = AssignRelation(relationName, relatedEntityId, asset);
                
                assetTypeToAssetRelation.Parent = relatedEntityId.Value;  
                await MClient.Entities.SaveAsync(asset).ConfigureAwait(false);  
                return true;
            }
        }
    }

    return false;
}