Publish an Image to a Structure Group Mirroring Folder

I often run into a scenario where clients ask to publish an image to a directory on the server.  Below is a TBB that does just this.  It publishes an image to a Structure Group that has the same relative path as the Folder. So you can right-click on a image and choose Publish in Tridion and then have it end up in, what appears to be the same folder on the delivery server (works great for untrained developers that just want to reference a src path).

This is not a best-practice approach because images and binaries are additions to content, not content themselves.  So the standard way of getting images published is actually to publish a piece of content that uses the image.  

However, sometimes clients choose to use Tridion as a repository, instead of a Content Management System, and aren’t concerned about best practices.  At the end of the day an army of cheap developer resources works better for some companies than a quality solution.  Nothing wrong with that, who am I to judge?  As one of my clients put it: “We just want to use Tridion as a ‘glorified paperboy’ to be able to move stuff to the server without the tedious Release Management process”.  So if you insist that this is what you want, then go for it.

Step 1:

Create a C# TBB with the following code and upload it into Tridion.  Here’s the code:

using System;
using Tridion.ContentManager.Templating;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.Templating.Assembly;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.ContentManagement.Fields;
using System.IO;

namespace ContentBloom.Tridion.Common.Templates
{
    /// 
    /// This TBB takes the component that is being published and places it in a
    /// specific structure group based on a TCM number or on WebDav path.
    /// 
    /// 
    [TcmTemplateTitle("Publish Dynamic Multimedia Item")]
    public class PublishDynamicMultimediaItem
    {
        public override void Transform(Engine engine, Package package)
        {

            // Declair the structure group that will hold the file
            StructureGroup sg = null;

            // Get the component from the package
            Component component = engine.GetObject(package.GetByName(Package.ComponentName).GetValue("ID")) as Component;

            Logger.Debug(String.Format("Publishing component: {0}", component));

            // Get the metadata fields from the folder to see if there is a specific structure group that needs the file, 
			// if the metafields are not tagged...skip
            string sguri = null;

            try
            {
                // Create the ItemFields to store the meta
                ItemFields folderMetaFields = new ItemFields(component.OrganizationalItem.Metadata, component.OrganizationalItem.MetadataSchema);

                // Pull out the structure group URI for pathing
                sguri = folderMetaFields["sguri"].ToString();
            }
            catch
            {
                //ignore
                Logger.Info("Structure Group URI is not specified, therefore proceeding with trying to publish to SG that mirror's the component's folder path");
            }

            Logger.Debug(String.Format("sguri: {0}", sguri));

            // IF the sguri is null or empty, try to WebDav path the item
            if (String.IsNullOrEmpty(sguri))
            {
                // Get the WebDav path of the component
                string compFolderWebDavUrl = component.OrganizationalItem.WebDavUrl;

                //Get the folder's relative path excluding "Building%20Blocks/Content"
                int indexOfRootFolder = compFolderWebDavUrl.IndexOf("Building%20Blocks/Content");
                string firstPath = compFolderWebDavUrl.Substring(0, indexOfRootFolder);
                string relativeFolderPath = compFolderWebDavUrl.Replace(firstPath + "Building%20Blocks/Content", string.Empty);

                // Get the publicaiton of the component
                Publication pub = component.ContextRepository as Publication;

                // Get the root structure group of the publiction for pathing
                string pubSGWebDavUrl = pub.RootStructureGroup.WebDavUrl;

                // Propend the folder path of the component to try and make the path
                string publishSGWebDavUrl = pubSGWebDavUrl + relativeFolderPath;

                // If the structrue group comes back null, throw an error that the WebDav path for publishing does not exist
                sg = engine.GetObject(publishSGWebDavUrl) as StructureGroup;

                if (sg == null)
                {
                    throw new InvalidDataException("The structure group mirroring the asset folder does not exist.  SG=" + publishSGWebDavUrl);
                }
            }
            else
            {
                // If a sguri was specified, try and get the structure group and catch if the sguri is not valid
                try
                {
                    sg = engine.GetObject(sguri) as StructureGroup;
                }
                catch (Exception e)
                {
                    throw new InvalidDataException("The structure group listed in the fodler metadata is not valid.  SG=" + sguri, e);
                }
            }

            // Push the structure group WebDav url to the package for safe keeping
            package.PushItem("sg", package.CreateStringItem(ContentType.Text, sg.WebDavUrl));

            // Create the template object
            Template template = engine.PublishingContext.ResolvedItem.Template;

            // Create the memory byte stream of the component
            MemoryStream ms = new MemoryStream();
            component.BinaryContent.WriteToStream(ms);

            // Get the file name of the component
            string filename = component.BinaryContent.Filename;

            //Strip off the file system path and only get the filename
            if (filename.Contains(@"\"))
            {
                int pos = filename.LastIndexOf(@"\");
                filename = filename.Substring(pos + 1);
            }

            //publish the binary under the variant ID of this template so that it doesn't clash with any binaries published for the
            //same multimedia component by a DWT (e.g. 2 Col Right Rail).
            engine.AddBinary(engine.LocalizeUri(component.Id), engine.LocalizeUri(template.Id), engine.LocalizeUri(sg.Id), ms.ToArray(), filename);
        }
    }
}

Step 2:

In Tridion create a Dynamic Component Template and name it “Publish Multimedia Components”.

Link to this CT the Multimedia Schemas that you wish to be able to publish this way.

Via Template Builder, add the above TBB as part of this new CT.

Step 3:

Right-click on the MM Component and publish.  You should shortly see your image end up on the server.

5 thoughts on “Publish an Image to a Structure Group Mirroring Folder

  1. Nice Post Nickoli,
    We now able to publish any Multimedia component on server. But what we need to do if we want to publish diffrent multimedia components(JS, image, Usercontrols, Css etc) to the diffrent folders in server, but through this tbb we can only able to publish to the same folder on server(Yes we can achieve by creating diffrent CTs for each type of MM components, but that would be the additional headache of creating lots of CTs).
    So here my requirement is to publish all MM components on diffrent folder loction on CD server. please provide your valuable inputs.

  2. Thanks for the code sample. I needed to update the code before it would build. Maybe you had a Base Class you inherited from, but removed it from the source before posting?

    Inherit from ITemplate
    public class PublishDynamicMultimediaItem : ITemplate

    And, create an instance of the Logger
    TemplatingLogger Logger = TemplatingLogger.GetLogger(this.GetType());

  3. Do you perhaps have an example of… and is it possible to publish a DCP instead of a Binary? … I think I had done this in the past, but cannot find my code, and also cannot get the EventSystem code to function:

    Session session = _engine.GetSession();
    List items = new List() { _engine.GetObject(attributeMatch.Groups[2].Value) as Component };
    List targets = new List { _engine.PublishingContext.PublicationTarget };
    PublishInstruction publishInstruction = new PublishInstruction(session);
    PublishEngine.Publish(items, publishInstruction, targets);
    //I GET THIS ERROR: You do not have permission to perform this action.

    Alternativly:
    Session session = _engine.GetSession();
    List items = new List() { _engine.GetObject(attributeMatch.Groups[2].Value) as Component };
    List targets = new List { new TargetType(_engine.PublishingContext.PublicationTarget.Id, session) };
    PublishInstruction publishInstruction = new PublishInstruction(session);
    PublishEngine.Publish(items, publishInstruction, targets, PublishPriority.Low);
    //I THEN GET THIS ERROR: Unexpected item type: PublicationTarget.

  4. Absolutely you could. In the example in the article, the binary technically is published as a blank DCP with the binary attached to it.

    The code should work, I pretty much copy-pasted it in a couple implementations. Looks like a permissions error with you Tridion account – perhaps you have access to work in folders, but not in SGs, or perhaps no publishing rights in the publication?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>