Monday, December 2, 2013

What is new in Tridion 2013 SP1 Workflow

Last week I was starting playing with the new workflow features coming with the new Tridion 2013 SP1 release. After revising all of them I will resume all of them in the following section.

First Activity Changes

In previous releases a workflow designer was able to only use a manual activity that is assigned to everyone as the first activity in the process definition. This limitation lead to multiple workarounds like using Event System to start and finish that first activity automatically, leading to some performance issues and unnecessary complexity during implementations.

Fortunately this limitation is gone and we are able to use an automatic activity as the first activity or having a manual activity assigned to an specific group or user.

Use cases for this new feature would be the following ones.
  • Merging multiple process definitions into a single one. For instance you can define a different workflow path for items coming from a different publication or with different metadata.
  • Having different workflows for certain groups.
  • Publish as soon as the workflow has been started then review.

 

Expiration Scripts

This new feature was visible in the Visio Workflow designer in Tridion 2013 GA but it was completely implemented in this release. This feature came with some API changes adding a new method to the ExternalActivity base class Expire this method will be executed when the Activity Due date is reached and the activity hasn’t been finished or even started.

I was doing some researching on it and it seems that the new method as added as part of a new interface IExternalActivity2013.

Here some sample implementation for an Expiration Script (very similar to a normal automatic activity script), one big thing to notice here is that you can specify an expiration script for a manual activity as well, so we can include some sort of automation for manual activities now.

 


  protected override void Expire() {
    SmtpClient client = new SmtpClient();
    MailMessage message = new MailMessage();
    message.From = new MailAddress("system@tridion.com");

    MailAddressCollection toAddress = new MailAddressCollection();
    toAddress.Add(new MailAddress(GetCurrentUserMail()));

    client.Send(message);
}

Approval Status for items added to Bundles

When an item is added to a bundle that is already in a workflow process instance, the added item will receive the current approval status, actually it will receive all the bundle workflow trace. For instance if you have a Bundle that has this workflow trace, Draft -> Staging -> Draft -> Staging, the added item will receive the same in its workflow process history.

Approval Status evaluation during publishing

Approval status is checked during publishing to make sure that the item being published meet at least the minimal approval status. This functionality is not new and has been there in tridion for several releases, so what is the difference?, Since Tridion 2013 GA we have the possibility to remove items from a Bundle regardless if it is in Workflow or not, it will lead to the possibility of removing an item from a bundle / workflow before it meets the minimal approval status for a publication target, then since it is not in workflow we were able to publish it even causing some inconsistency. In this release we are evaluating the approval status even if the item is not in workflow so that we can ensure that just content with the required approval level reaches the publication target.

Tuesday, November 26, 2013

Tridion Workflow - Developing using Dependency Injections

During a workflow design session I noticed that sometimes using a classes design just based on inheritance is not enough and not as flexible as I expected. You can refer to a previous post to get some background Developing Tridion 2013 Workflows.
In that previous post I was reusing code by having an abstract class called BaseActivity and then creating more specialized classes like PublishActivity and RejectActivity that inherit from BaseActivity. This design allows me to reuse and to create specialized functionality for my activities.
There is a concept that I remember from my days studying computer engineering at the University (old and fun days) “Favor composition over inheritance” – Thanks Alvin Reyes for refreshing my mind. Why composition and why not inheritance, well Inheritance is not bad but if we can combine it with composition then we get an stronger result.
I decided to integrate an IoC Container to my workflow implementation so that I can instantiate my objects members (composition) at runtime, giving flexibility and scalability to my implementation. I used Ninject as my IoC container because it is light and easy to use.
Integrating Ninject with a Tridion Workflow Implementation
The following classes design clearly shows two classes hierarchies that are not linked and can be developed / extended independently.

In order to bind those two classes hierarchies I will use Ninject to inject dependencies at runtime based on the class composition.
Binding Activity Classes with Worker Classes
1.      Declare loosely coupled links in the activity classes.
namespace Tridion.ContentManager.Spark.Workflow.Publish {
    public class PublishToDraftActivity : BaseActivity {
       
protected Publisher publisher;
protected Notifier notifier;
}
}
Notice that I am declaring abstract types in order to avoid linking the activity class with a concrete implementation.
2.      Declare a constructor that will receive concrete instances of abstract members.
namespace Tridion.ContentManager.Spark.Workflow.Publish {
    public class PublishToDraftActivity : BaseActivity {
        protected Publisher publisher;
        protected Notifier notifier;

        public PublishToDraftActivity(Publisher publisher, Notifier notifier) {
            this.publisher = publisher;
            this.notifier = notifier;
        }
    }
}
  
            3.      Define injection rules, in this implementation I am using a Ninject module, however you can use an XML file or Ninject naming conventions.

namespace Tridion.ContentManager.Spark.Workflow.Modules {
    public class InjectionModule : NinjectModule {
        public override void Load() {
            Bind<Publisher>().To<StandardPublisher>();
            Bind<Notifier>().To<EmailNotifier>();
        }
    }
}

The injection rules above will bind any Publisher reference to StandardPublisher and any Notifier reference to EmailNotifier. Here the beauty of Dependencies Injections, we can change bindings and it doesn’t affect the activities implementation. For instance we can decide to notify using Rss instead of sending an email, then we should change the binding to use RssNotifier instead of EmailNotifier.


Creating Activity Classes Instances – Linking all together at runtime.

In order to accomplish this I have created a new Workflow Script Executor that will use the Ninject Kernel to create instances instead of System.Reflection. You can get information about how to create your own Workflow Script Executor in this post Extending Workflow Scripts

The Ninject Kernel will instantiate Activity Instances and “Apply” the injection rules when the object is being constructed (the magic happens in the constructor).

External Activity Script
AssemblyTbbId = "tcm:2-49-2048"
Type = "Tridion.ContentManager.Spark.Workflow.Publish.PublishToDraftActivity"

private IExternalActivity GetExternalActivity(string script, bool isExpiration, string currentActivityInstanceId) {
    IEnumerable<Match> matches = _nameValuePairRegex.Matches(script).Cast<Match>();
    Match assemblyMatch = matches.FirstOrDefault(m => m.Groups["Name"].Value.ToUpperInvariant().Trim() == _assemblyTbbIdParam);
    Match typeMatch = matches.FirstOrDefault(m => m.Groups["Name"].Value.ToUpperInvariant().Trim() == _typeParam);

    TcmUri uri = new TcmUri(assemblyMatch.Groups["Value"].Value);
    string typeName = typeMatch.Groups["Value"].Value;

    StreamDownloadClient downloadChannel = new StreamDownloadClient(TcmConstants.LatestStreamDownloadNetTcpEndPointName);
           

    FullVersionInfo fullVersionInfo = (FullVersionInfo)buildingBlockData.VersionInfo;           
    byte[] bytes = ReadContentBytes(downloadChannel.DownloadBinaryContent(uri));
    Assembly assembly =
TemplateAssemblyCache.GetCachedAssembly(bytes, uri, new TcmUri(buildingBlockData.BluePrintInfo.OwningRepository.IdRef), fullVersionInfo.Version.Value, fullVersionInfo.Revision.Value);

    Type type = assembly.GetType(typeName);
    IKernel kernel = new StandardKernel();
    kernel.Load(assembly);

    return (IExternalActivity)kernel.Get(type, contextDataParameter);
}

The Kernel will execute the Injections Modules and will glue everything while the object is being constructed.