Skip to main content

Publish Binaries to Mapped Structure Groups

Today's TBB of the Week comes from the high demand in the field to publish binary assets to different mapped Structure Groups. By default SDL Tridion offers two ways of publishing binaries:
  1. All binaries publish to a folder defined in your Publication properties;
  2. All binaries rendered by a given template publish to a folder corresponding to a given Structure Group;
In my view, both cases are terrible, over-simplified and not representing a real use-case. Nobody in the field wants all binaries in one folder and nobody separates binary locations by template. Instead, everybody wants a mapping mechanism that takes a binary and publishes it to a given folder, defined by a Structure Group, and this mapping is done using some kind of metadata.

More often than not, the metadata is the TCM Folder location of the Multimedia Component. I have seen this implemented numerous times. So the solution to publish binaries to a given location implies finding a mapping from a TCM Folder to a Structure Group.

Normally, a Multimedia Component resides in a Folder, so that is given. However, the corresponding Structure Group does not exist. So, first, the Structure Group structure has to be created (this will be the subject of a future post). Typically we write an event system that creates the Structure Group structure upon the Multimedia Component save. The structure of these Structure Groups follows 1-to-1 the structure of TCM Folders where the MMC resides in.

Second, we publish the binary to the mapped Structure Group. Let's assume the Structure Group has been created and what we need is to find it. This is the goal of today's TBB, which was originally written by my colleague Eric Huiza, so credits go to him. Hope you understand the code :)

Description

Name
Publish Binary TBB
Type
·    Template in .NET Assembly
Description
Used to:
·    Publishes a Binary to a given Structure Group;
Notes:
This TBB publishes the current Component.
It expects the current Component in the Package is a Multimedia Component.
It publishes the Component to a Structure Group that is mapped to the Multimedia Component’s Folder location.
The Structure Group structure must exist before hand (created by event system) – this TBB will not create any SGs.
Parameters
TBB uses the following items in the Package:
·    AssetRootFolder – TcmUri of the Folder acting as root for the binary assets;
·    AssetRootStructureGroup – TcmUri of the Structure Group acting as root for binary assets;
Applicable to
Component Templates

This TBB goes well with Read Configuration Items TBB - used to configure the AssetRootFolder and AssetRootStructureGroup parameters.

The Code

In its purest form, this TBB does nothing more than calling a utility method:

[TcmTemplateTitle("Publish Binary TBB")]
public class PublishBinary : ITemplate {

    public void Transform(Engine engine, Package package) {
        Item componentItem = package.GetByName(Package.ComponentName);
        Component component = engine.GetObject(componentItem) as Component;

        if (component.BinaryContent != null) {
            GenericUtils utils = new GenericUtils();
            utils.PublishBinary(engine, package, component);
        }
    }
}

The real implementation is in the utility method PublishBinary:

public Binary PublishBinary(Engine engine, Package package, Component multimediaComponent) {
    TcmUri origUri = multimediaComponent.Id;
    TcmUri localUri = engine.LocalizeUri(origUri);
    if (!origUri.Equals(localUri)) {
        multimediaComponent = engine.GetObject(localUri) as Component;
    }

    BinaryContent binaryContent = multimediaComponent.BinaryContent;
    if (binaryContent == null) {
        return null;
    }

    Session session = engine.GetSession();
    TcmUri rootFolderUri = new TcmUri(package.GetValue("AssetRootFolder"));
    TcmUri rootStructureGroupUri = new TcmUri(package.GetValue("AssetRootStructureGroup"));

    Folder rootFolder = new Folder(rootFolderUri, session);
    StructureGroup rootStructureGroup = new StructureGroup(rootStructureGroupUri, session);

    Stack<string> structureGroupsLookUp = new Stack<string>();
    PopulateStructureGroupsForLookUp((Folder)multimediaComponent.OrganizationalItem, rootFolder, structureGroupsLookUp);

    OrganizationalItemItemsFilter structureGroupsFilter = new OrganizationalItemItemsFilter(session);
    structureGroupsFilter.Recursive = false;
    structureGroupsFilter.ItemTypes = new Tridion.ContentManager.ItemType[] { ItemType.StructureGroup };

    StructureGroup publishStructureGroup = GetStructureGroupForPublication(rootStructureGroup, structureGroupsLookUp, structureGroupsFilter);
    Binary binary = engine.PublishingContext.RenderedItem.AddBinary(multimediaComponent, publishStructureGroup,
        Regex.Replace(binaryContent.Filename, "\\W", string.Empty));

    return binary;
}

The logic first populates (finds) the mapped Structure Groups corresponding to each element in the binary's Folder path. The implementation is here:

public void PopulateStructureGroupsForLookUp(Folder currentFolder, Folder rootFolder, Stack<string> structureGroupsLookUp) {
    if (currentFolder != null && currentFolder.Id != rootFolder.Id) {
        structureGroupsLookUp.Push(currentFolder.Title);
        PopulateStructureGroupsForLookUp((Folder)currentFolder.OrganizationalItem, rootFolder, structureGroupsLookUp);
    }
}

Note: the code above does not check for invalid characters in the Folder Title. Such characters might prevent the identification of the right Structure Group. For example, characters not allowed in a folder/file name by the operating system.

Second, the mapped Structure Group is used to publish to:

public StructureGroup GetStructureGroupForPublication(StructureGroup rootStructureGroup, Stack<string> structureGroupsLookUp, OrganizationalItemItemsFilter filter) {
    while (structureGroupsLookUp.Count > 0) {
        string structureGroupName = structureGroupsLookUp.Pop();

        StructureGroup childStructureGroup = rootStructureGroup.GetItems(filter).
            Cast<StructureGroup>().
            Where(w => w.Title.Equals(structureGroupName)).
            FirstOrDefault();

        if (childStructureGroup == null) {
            break;
        } else {
            rootStructureGroup = childStructureGroup;
        }
    }

    return rootStructureGroup;
}

A strange side-effect of this code (may be seen as a feature, actually) is that in the case when the mapped Structure Group does not exist (for some reason), it will in fact use the closest existing Structure Group relative to the missing desired SG.


Comments

Anonymous said…
thank you for sharing ...
Is it possible to publish binary to a folder structure rather structure group.
Add binary methods accepts parameter of structure group only..
I need your suggestion on publishing to folder structure.
Please suggest.
Anonymous said…
Addition to my question... need to move all .gifs to GIFfolder and rest other type to MISCfolder
Mihai Cădariu said…
Using the provided Tridion API, you can only publish to a Structure Group. That in turn maps to a folder on the file system of the Presentation Server.

However, if you are looking for the ability to publish to a Tridion Folder, that's not possible.

There are ways to integrate/extend the logic, but it would be a very specific custom solution.
Mihai Cădariu said…
For publishing your GIFs to a certain folder, you will need to map a particular Structure Group for all your GIFs.

For example, you can do that programmatically by looking at the Multimedia Component filename extension and map it to a certain Structure Group. Have a look at my other post as well for an example mapping logic http://yatb.mitza.net/2013/01/event-system-to-create-mapped-structure.html
Anonymous said…
I have created structure group in the parent publication. When I publish a binary from the relevant child publication I am not able to publish the binary to the in herited structure group in the child.
Please suggest is do able or any solution for this..
Mihai Cădariu said…
There is no limitation with publishing to an inherited SG.

Make sure you are in fact publishing from the correct Publication and that you are referring the SG within that particular Publication.
Unknown said…
Hi Mihai

Thanks for the article. I have a question, is it possible to override the publication path while calling the AddBinary API. In my case it always uses the path mentioned in the publication metadata.

Thanks
Anonymous said…
Hi Mihai,

Thank you for the information.

I came accross a scenario and I wanted to share with you. Please suggest if you have answers

My TBB creates a structure group and move the binaries into it if the SG already exist for the second time it just moves the binaries. Incase if I delete the structure and re try the publish my TBB again create a structure group with the same expected name and try to move the binaries and this time it thows an error "Attempting to deploy a binary xx to a different location while still in use by deployeed items that are not being redeployed in the transaction. ".
Mihai Cădariu said…
That deployer error only happens if the Directory of the SGs have somehow changed. Check the path of the Binary in the CD DB or the path on file system and compare it to your path given by the 'newly created SGs'. Somewhere there must be a difference. Make sure that the SG path matches identically the Binary path.
Mihai Cădariu said…
Hi Vinoth -- late reply, I know :)

When publishing to an SG using AddBinary, the binary will placed under the root path and root URL specified in the Publication properties. There is no way to change that from the API (unless you go in modifying it in some Deployer/Storage extension).

The behaviour makes sense -- you are actually publishing to the directory defined by the SG. That includes the Publication path from Publication properties (and Publication URL if you are looking at the URLs).

Popular posts from this blog

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as

Content Delivery Monitoring in AWS with CloudWatch

This post describes a way of monitoring a Tridion 9 combined Deployer by sending the health checks into a custom metric in CloudWatch in AWS. The same approach can also be used for other Content Delivery services. Once the metric is available in CloudWatch, we can create alarms in case the service errors out or becomes unresponsive. The overall architecture is as follows: Content Delivery service sends heartbeat (or exposes HTTP endpoint) for monitoring Monitoring Agent checks heartbeat (or HTTP health check) regularly and stores health state AWS lambda function: runs regularly reads the health state from Monitoring Agent pushes custom metrics into CloudWatch I am running the Deployer ( installation docs ) and Monitoring Agent ( installation docs ) on a t2.medium EC2 instance running CentOS on which I also installed the Systems Manager Agent (SSM Agent) ( installation docs ). In my case I have a combined Deployer that I want to monitor. This consists of an Endpoint and a

Debugging a Tridion 2011 Event System

OK, so you wrote your Tridion Event System. Now it's time to debug it. I know this is a hypothetical situtation -- your code never needs any kind of debugging ;) but indulge me... Recently, Alvin Reyes ( @nivlong ) blogged about being difficult to know how exactly to debug a Tridion Event System. More exactly, the question was " What process do I attach to for debugging even system code? ". Unfortunately, there is no simple or generic answer for it. Different events are fired by different Tridion CM modules. These modules run as different programs (or services) or run inside other programs (e.g. IIS). This means that you will need to monitor (or debug) different processes, based on which events your code handles. So the usual suspects are: dllhost.exe (or dllhost3g.exe ) - running as the MTSUser is the SDL Tridion Content Manager COM+ application and it fires events on generic TOM objects (e.g. events based on Tridion.ContentManager.Extensibility.Events.CrudEven