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:
This TBB goes well with Read Configuration Items TBB - used to configure the AssetRootFolder and AssetRootStructureGroup parameters.
The real implementation is in the utility method PublishBinary:
The logic first populates (finds) the mapped Structure Groups corresponding to each element in the binary's Folder path. The implementation is here:
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:
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.
- All binaries publish to a folder defined in your Publication properties;
- 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;
}
Comments
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.
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.
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
Please suggest is do able or any solution for this..
Make sure you are in fact publishing from the correct Publication and that you are referring the SG within that particular Publication.
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
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. ".
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).