Wednesday, September 24, 2014

Developing a Custom Transport Handler

In this blog post I wanted to show a more advanced feature available in SDL Tridion, this feature gives developers a more deeper control on the Transport Package. Unfortunately this nice feature is neither documented nor supported but I think it is good enough to show how to use it in a Blog Post.

Developing a Transport Handler is very similar to developing other Tridion extension points like Resolvers or Event Systems. So, let’s provide a list of steps in order to develop one. In this sample I am writing a Transport Handler that overrides the existing TaxonomyTransportHandler in order to add the user that published the Category in the Instructions document.

1.       Implement a class that inherits ITransportHandler interface. This interface is not available in the DLLs available in /bin/client, you have to reference Tridion.ContentManager.Publishing.Transporting.dll that is in the GAC

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Tridion.ContentManager.Publishing.Rendering;
using Tridion.ContentManager.Publishing.Resolving;
using Tridion.ContentManager.Publishing.Transporting;

namespace TransportHandlers {
    public class CustomTaxonomyTransportHandler : ITransportPackageHandler {

        private TaxonomyTransportPackageHandler _defaultTaxonomyHandler;
        private XmlDocument _defaultInstructionsDocument;
        private string _user;

        public CustomTaxonomyTransportHandler(TransportPackage package) {
            _defaultTaxonomyHandler = new TaxonomyTransportPackageHandler(package);
            _defaultInstructionsDocument = package.InstructionsDocument;
            _user = package.PublishTransaction.Creator.Title;
        }

        public void HandleItemForPublishing(object item, XmlElement parentElement) {
            _defaultTaxonomyHandler.HandleItemForPublishing(item, parentElement);
        }

        public XmlElement HandleRenderedItemForPublishing(RenderedItem renderedItem, XmlElement parentElement) {
            XmlElement customCategoryElement = _defaultInstructionsDocument.CreateElement("CustomCategoryInfo");
            customCategoryElement.InnerXml = string.Format("<Category>{0}</Category><User>{1}</User>", renderedItem.ResolvedItem.Item.Id, _user);
            _defaultInstructionsDocument.DocumentElement.AppendChild(customCategoryElement);

            return _defaultTaxonomyHandler.HandleRenderedItemForPublishing(renderedItem, parentElement);
        }

        public void HandleResolvedItemForUnPublishing(ResolvedItem resolvedItem) {
            _defaultTaxonomyHandler.HandleResolvedItemForUnPublishing(resolvedItem);
        }
    }
}


2.       The class above should be compiled in an Strong Name Assembly and registered in the GAC
3.       Register it in the Tridion.ContentManager.config configuration file

<transporting rootStorageFolder="c:\Temp">
  <mappings>
    .
    .
    .
    <add itemType="Tridion.ContentManager.ContentManagement.Category">
      <handler type="TransportHandlers.CustomTaxonomyTransportHandler" assembly="TransportHandlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3b4b2f6e0f11afc8" />
    </add>
    .
    .
    .
  </mappings>
</transporting>

4.       Restart the TcmPublisher Windows Service
5.       Publish any Publishable Category, as a Result you will see a custom Xml Element appended in the Transport Package Instructions document

<ProcessorInstructions version="7.1.0.1290">
  <Publication Id="tcm:0-3-1" Title="400 Example Site" Key="400 Example Site" PublicationPath="\" PublicationUrl="/" MultimediaPath="\media" MultimediaUrl="/media/" />
  <Action>Deploy</Action>
  <Target Type="6" />
  <CustomCategoryInfo>
    <Category>tcm:3-29-512</Category>
    <User>T2013SP1\Administrator</User>
  </CustomCategoryInfo>
  <Section Type="Taxonomies" Name="Taxonomies">
    <Taxonomy Id="tcm:3-29-512" Name="tcm_3-29-512.xml" UseForIdentification="False" />
  </Section>
</ProcessorInstructions>

Monday, September 22, 2014

ILMerge and SDL Tridion Development

SDL Tridion comes with a nice feature that allows developers to upload DLLs as managed Assemblies (Template Building Blocks, Workflow Activities) or to reference DLLs in the file system (Event System, Resolvers). While implementing SDL Tridion we normally have to re-use projects or external assemblies across different areas in our implementation which is normal in modular software development.

The fact of re-usability is mandatory and key in any software implementation. .NET code is executed in boundaries called Application Domains, at execution time the .NET Framework creates an Application Domain which will contain the executing assembly and any other dependency (Assemblies and Resources). The .NET Framework looks for dependencies in the following places.

1.     Checks for assemblies referenced in Configuration Files (<runtime> element)
2.     Checks for assemblies in the same Folder
3.     Check for assemblies in the Global Assembly Cache

However the way SDL Tridion executes code brings some issues. .NET TBBs and Workflow Activities are dynamically loaded from records in the Content Manager database. It means we cannot reference assemblies in the same folders or dependencies in configuration files (because there is no folder or configuration file involved), it leaves GAC as the only option

So, the question is, What are my alternatives when I want to use a shared library or project between different areas in our implementation?, for instance I have developed a Logging Framework or XML Parsing libraries that are used by Event System and TBBs as well as Workflow Activities. In that scenario the only option is to register those shared libraries in the GAC (Global Assembly Cache) which makes deployment process more difficult and managing dependencies even more complicated.

ILMerge allows to merge different assemblies (libraries, projects) into a single assembly which in fact will make all the code and functionality available in different libraries and projects in the same Application Domain. With ILMerge we can have a logging library or project references in Workflow Activities as well as referenced in TBBs. ILMerge will generate a single assembly that contains the result of merging different assemblies (executing assemblies and dependencies) in the same DLL so we don’t need to worry about registering all the shared dependencies in the GAC.

DD4T and SDL Reference Implementation are using that approach in order to make compilation and deployment easier.


Here a sample on how DD4T templates are compiled using ILMerge. This sample shows the Post Build Event configured in the DD4T Template Project

Command Segment
Description
"C:\Development\dynamic-delivery-4-tridion-master\dotnet\Dynamic Delivery Publishing Solution Items\ilmerge"
ILMerge location
/lib:"C:\Windows\Microsoft.NET\Framework\v4.0.30319"
/lib:"C:\Program Files (x86)\Tridion\bin\client"
Use a /lib parameter in order to specify paths where ILMerge will look for dependencies that should be merged in the resulting assembly
/t:dll
Target Format (DLL, EXE)
/targetplatform:v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319
Specifies that the DLL is intended for the .NET Framework 4 and higher
/out:"C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.Templates.merged.dll"
Resulting merged DLL path
"C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.ContentModel.Contracts.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.ContentModel.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.Templates.Base.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.ContentModel.XmlSerializers.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.Templates.dll"
The list of assemblies that should be merged

"C:\Development\dynamic-delivery-4-tridion-master\dotnet\Dynamic Delivery Publishing Solution Items\ilmerge"
/lib:"C:\Windows\Microsoft.NET\Framework\v4.0.30319"
/lib:"C:\Program Files (x86)\Tridion\bin\client"
/t:dll
/targetplatform:v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319
/out:"C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.Templates.merged.dll"
"C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.ContentModel.Contracts.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.ContentModel.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.Templates.Base.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.ContentModel.XmlSerializers.dll" "C:\Development\dynamic-delivery-4-tridion-master\build\DD4T.Templates.dll"