Crocodoc .NET API Out Of Beta

My colleague Frank Taylor and I are happy to announce that Crocodoc v1.0 for .NET is now out of Beta testing and can be downloaded from its Google Code site. Thanks to everyone who has participated in the 1.0 Beta testing and who has provided feedback or reported any issues.

And for those who may be reading this article wondering what this .NET library is for, check out my previous blog post about its beta release, Frank’s blog entry, or even the Crocodoc site itself.

Besides fixes, only one (overloaded) method has been added to this release. While the previous CrocodocDocument.Upload(string filePath) allowed you to specify a complete file path for you to upload, the method CrocodocDocument.Upload(string fileName, byte[] binary) allows you to upload a document’s byte array directly. This could be useful for when uploading files not from the file system, but say a database or content management system.

Please continue to either contact Frank or myself with any feedback, suggestions, or further issues found. And happy document embedding everyone!

Razor Mediator Version 1.3.2 Released

A new minor version of the Razor Mediator has been released and can be found at the Google Code site. For anyone who is interested in installing Razor Mediator on Tridion 2013, you will find this release especially important to you. Prior to this version, the Installer will throw an error and the installation will fail.

Besides being able to install without an error, this version also changes how the configuration is done slightly. Prior to 1.3.2, the template ID for Razor Mediator was generated during installation by selecting the highest free available ID. With an out of the box Tridion setup, this would normally have resulted in a template type of “8″. Tridion 2013 comes with a new XSLT Mediator, but they have left the ID’s of 8 and 9 empty. So, installation for 1.3.2 will attempt to use 8 if its available, otherwise it’ll pick the ID just like it use to do.

An important thing to note is that you may have to manually modify this ID if you are porting from another system that used a different ID for the Mediator. If the ID’s don’t match up, you will get an error during the content porter process.

Thanks to Nicholas Vander Ende, Frank Taylor, and Piti Itharat for reporting and troubleshooting the installation error in 2013. A special thanks to Nicholas for actually finding the fix to the problem as well.

ComponentPresentations and ComponentTemplateModel

Thanks to Chris Curry for spotting that ComponentPresentationModel’s Template property was not returning a ComponentTemplateModel type, but rather just the Tridion’s ComponentTemplate type. This means you would have to grab the ItemFields in order to fetch fields. This version fixes the CompoenntPresentationModel’s Template property.

@foreach (var cp in ComponentPresentations) {
    <div>cp.Template.Fields.FieldName</div>
}

Get Version From Template

Although you can get the Razor Mediator version by looking at the Tridion.ContentManager.config file, sometimes you may not have access to the server and need another quick way to get the version. 1.3.2 comes with a “Version” property in the base template that you can output to check the version.

    <div>Version: @Version</div>

Models.GetComponentTemplate

The ModelUtilities class now comes with a GetComponentTemplate method to easily pull out ComponentTemplateModels.

@{
    var ct = Models.GetComponentTemplate("tcm:1-2345-32");
}

Thanks again to everyone for your feedback and suggestions!

Changing Components’ Schemas With Core Service

Besides making new extensions and applications to extend the features of Tridion, sometimes the Core Service API is a great tool for scripting something that would otherwise take a long time to do manually. A colleague of mine is in need of such a script, and has never worked with the Core Service API before, so I thought I would write this tutorial to help him get on the right track.

The project he’s in has the need to change the schema for a bunch of multi-media components. The GUI does not provide a way to change the schema, as changing schemas for components could lead to data loss. For example, if your metadata values have different names than the schema that you are switching to, data in those fields would be gone. Fortunately for my colleague, there is no metadata on the old schemas.

As my colleague is somewhat new to the world of .NET as well, this tutorial may explain some of the basics of .NET and Visual Studio as well.

The Project Setup

To start, create a new .NET Console Application project in Visual Studio (for this tutorial, I am using Visual Studio 2010 Professional Edition). Lets call it “Examples.ComponentSchemaChanger”. Visual Studio will create a new project with a Program class that contains a static void Main(string[] args) method. This Main method is the entry point for your console application. The args parameter is arguments that you can pass to your application from the command line.

Next, create a folder in Windows in the Solution folder that was created called “Resources”. Here we’ll include all referenced assemblies that our project will use. Since this is a fairly simple script, only one will be needed.

On a Tridion server (or VM), from the /bin/client/ directory of the location where Tridion is installed. Copy “Tridion.ContentManager.CoreService.Client.dll.config” and “Tridion.ContentManager.CoreService.Client.dll” from this location to the Resources folder you created in the previous step. The DLL contains the Core Service API that you will use for this script, and the config contains the configuration bindings that your application will use to connect to Tridion.

Next, lets add the reference to the DLL. Right click the References folder in your Visual Studio project, and select “Add Reference”. Select the “Browse” tab, and then browse to your Resources folder. Select “Tridion.ContentManager.CoreService.Client.dll” and click “Ok”.

You’ll also need to add several of other references. Right click and add references, but this time select the “.NET” tab and add references to “System.Configuration”, “System.ServiceModel” and “System.Runtime.Serialization”.

Now that we have referenced the Core Service library, lets modify the configuration. Right click the project, and select “Add -> New Item”. Under the General section, select “Application Configuration File”. Next copy the contents of “Tridion.ContentManager.CoreService.Client.dll.config” to your “App.config” folder. Right underneath the <configuration> element in your App.config file, add the following:

  <appSettings>
    <add key="CoreServiceEndpoint" value="netTcp_2011" />
    <add key="TridionUsername" value="YOUR TRIDION USERNAME" />
    <add key="TridionPassword" value="YOUR TRIDION PASSWORD"/>
  </appSettings>

The above will let you set the username and password that your script will use to authenticate with the Core Service, as well as which endpoint name to use (more on that later).

The SchemaChanger Class

Let’s add a new class to the project called “SchemaChanger”. Right click the project, click “Add -> Class” and then give it the name “SchemaChanger”.

Now add the following field to your new class:

private SessionAwareCoreServiceClient _client;

You’ll see an error squiggly line and a message of “The type or namespace name ‘SessionAwareCoreServiceClient’ could not be found (are you missing a using directive or an assembly reference?)”. This is because we have not referenced the namespace in a using statement. Let’s let Visual Studio do this automatically for us. Click the squiggly lined “SessionAwareCoreServiceClient”, and when the down arrow appear, click that, and then “using Tridion.ContentManager.CoreService.Client;“. You’ll see the using statement automatically added to the top, and your error message has gone away. For the rest of this tutorial, do the same to any similar errors you encounter to automatically add the the namespaces with the using directive.

Next, let’s add a property to our class that will use a getter accessor for getting our client.

public SessionAwareCoreServiceClient Client
{
    get
    {
        if (_client == null)
        {
            string endpointName = ConfigurationManager.AppSettings["CoreServiceEndpoint"];
            if (String.IsNullOrEmpty(endpointName))
            {
                throw new ConfigurationErrorsException("CoreServiceEndpoint missing from appSettings");
            }

            _client = new SessionAwareCoreServiceClient(endpointName);

            string username = ConfigurationManager.AppSettings["TridionUsername"];
            string password = ConfigurationManager.AppSettings["TridionPassword"];

            if (!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password))
            {
                _client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(username, password);
            }
        }
        return _client;
    }
}

What the above is doing is, if our _client variable is null, to create a new instance of SessionAwareCoreSerivceClient using the endpoint that we supply in our appSettings. If _client has already been instanciated, it’ll return it without creating a new instance. We’ll also be using the TridionUsername and TridionPassword appSettings that we set up earlier. If the appConfig does not have these settings, or if they are empty, the application will attempt to use the user that will be running the script. It also checks to make sure the CoreServiceEndpoint appSetting exists, and throws an error if its missing.

Next, lets add a method that’s going to be doing the work for us. Let’s call this method “ChangeSchemasForComponentsInFolder”, and allow it to take 4 arguments: one for the TcmUri of the folder we want to search in, one to allow the operation to happen recursively, the third one for the TcmUri of the schema that we want to change from, and the forth one for the TcmUri of the schema that we want to change the components to.

public void ChangeSchemasForComponentsInFolder(string folderUri, bool recursive, string fromSchemaUri, string schemaUriToChangeTo)
{

}

We can only modify components that aren’t already checked out, so lets create a variable that will keep track of components that we are not able to edit.

    List<ComponentData> failedItems = new List<ComponentData>();

Next we’ll want to open the folder that we are going to search in. We can use the CoreService client’s Read method for this.

    FolderData folder = Client.Read(folderUri, null) as FolderData;

We’ll also want to grab the Schema that we’ll be switching to, and the namespace of that schema.

    SchemaData schema = Client.Read(schemaUriToChangeTo, null) as SchemaData;
    XNamespace ns = schema.NamespaceUri;

Now we’ll create a filter to actually query for multimedia items. We’ll want to make sure to only grab Components, and to only grab components of a Multimedia type. We’ll also want to check for components recursively if we’ve supplied to do so in the passed argument.

    OrganizationalItemItemsFilterData filter = new OrganizationalItemItemsFilterData();
    filter.ItemTypes = new ItemType[] { ItemType.Component };
    filter.ComponentTypes = new ComponentType[] { ComponentType.Multimedia };
    filter.Recursive = recursive;

And finally the actual work of switching the schema of the component. We’ll open up the component in read mode first, and only check it out to modify if the component’s current schema ID matches the schema we want to change from. If the component doesn’t match, we’ll attempt to check it out. If we are successful on checking it out, we’ll change its schema, save, and check back in. If we weren’t successful on checking the item out, we’ll make note of that item to report at the end.

    XElement items = Client.GetListXml(folder.Id, filter);
    foreach (XElement item in items.Elements())
    {
        ComponentData component = Client.Read(item.Attribute("ID").Value, null) as ComponentData;

        if (!component.Schema.IdRef.Equals(fromSchemaUri))
        {
            // If the component is not of the schmea that we want to change from, do nothing...
            continue;
        }

        component = Client.TryCheckOut(component.Id, new ReadOptions()) as ComponentData;

        if (component.IsEditable.Value)
        {
            component.Schema.IdRef = schemaUriToChangeTo;
            component.Metadata = new XElement(ns + "Metadata").ToString();
            Client.Save(component, null);
            Client.CheckIn(component.Id, null);
            Console.WriteLine(String.Format(" - changed schema for {0} ({1}) ", component.Title, component.Id));
        }
        else
        {
            failedItems.Add(component);
        }
    }

This is where the namespace of the schema comes into play with the component.Metadata = new XElement(ns + "Metadata").ToString() Here we are setting up an empty metadata section using the namespace from the Schema that we are switching to. Without this line, you might see an error like “Root element must be in namespace”.

What about that failedItems variable that we were keeping track of? Good catch. Let’s go ahead and report the items that we weren’t able to change by adding the following to the end of our method.

    if (failedItems.Count > 0)
    {
        Console.WriteLine();
        Console.WriteLine("The following items could not be converted. Please have them checked in and try again.");
        foreach (ComponentData component in failedItems)
        {
            Console.WriteLine(component.Id + " :" + component.LocationInfo.WebDavUrl);
        }
    }

Our SchemaChanger class is now complete, and should look something like the following:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using System.Xml.Linq;
using Tridion.ContentManager.CoreService.Client;

namespace Examples.ComponentSchemaChanger
{
    /// <summary>
    /// Script example of changing schemas for multi media components.
    /// </summary>
    class SchemaChanger
    {
        /// <summary>
        /// The session aware core service client.
        /// </summary>
        private SessionAwareCoreServiceClient _client;

        /// <summary>
        /// Gets the SessionAware Core Service client.
        /// </summary>
        public SessionAwareCoreServiceClient Client
        {
            get
            {
                if (_client == null)
                {
                    string endpointName = ConfigurationManager.AppSettings["CoreServiceEndpoint"];
                    if (String.IsNullOrEmpty(endpointName))
                    {
                        throw new ConfigurationErrorsException("CoreServiceEndpoint missing from appSettings");
                    }

                    _client = new SessionAwareCoreServiceClient(endpointName);

                    string username = ConfigurationManager.AppSettings["TridionUsername"];
                    string password = ConfigurationManager.AppSettings["TridionPassword"];

                    if (!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password))
                    {
                        _client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(username, password);
                    }
                }
                return _client;
            }
        }

        /// <summary>
        /// Changes schemas for multimedia components located in a specific folder.
        /// </summary>
        /// <param name="folderUri">The tcm uri of the folder to change the items in.</param>
        /// <param name="recursive">Whether or not to perform this recursively on child folders.</param>
        /// <param name="fromSchemaUri">The tcm uri of the schema that we are changing from.</param>
        /// <param name="schemaUriToChangeTo">The tcm uri of the schema to change the components to.</param>
        public void ChangeSchemasForComponentsInFolder(string folderUri, bool recursive, string fromSchemaUri, string schemaUriToChangeTo)
        {
            // Let's keep track of items that couldn't be checked out and report at the end.
            List<ComponentData> failedItems = new List<ComponentData>();

            // Open the folder to check
            FolderData folder = Client.Read(folderUri, null) as FolderData;

            // Open up the schema that we will be changing to.
            SchemaData schema = Client.Read(schemaUriToChangeTo, null) as SchemaData;
            XNamespace ns = schema.NamespaceUri;

            // Create a filter to get all multi-media components.
            OrganizationalItemItemsFilterData filter = new OrganizationalItemItemsFilterData();
            filter.ItemTypes = new ItemType[] { ItemType.Component };
            filter.ComponentTypes = new ComponentType[] { ComponentType.Multimedia };
            filter.Recursive = recursive;

            XElement items = Client.GetListXml(folder.Id, filter);
            foreach (XElement item in items.Elements())
            {
                ComponentData component = Client.Read(item.Attribute("ID").Value, null) as ComponentData;

                if (!component.Schema.IdRef.Equals(fromSchemaUri))
                {
                    // If the component is not of the schmea that we want to change from, do nothing...
                    continue;
                }

                if (component.Schema.IdRef.Equals(schema.Id))
                {
                    // If the component already has this schema, don't do anything.
                    continue;
                }

                component = Client.TryCheckOut(component.Id, new ReadOptions()) as ComponentData;

                if (component.IsEditable.Value)
                {
                    component.Schema.IdRef = schemaUriToChangeTo;
                    component.Metadata = new XElement(ns + "Metadata").ToString();
                    Client.Save(component, null);
                    Client.CheckIn(component.Id, null);
                    Console.WriteLine(String.Format(" - changed schema for {0} ({1}) ", component.Title, component.Id));
                }
                else
                {
                    failedItems.Add(component);
                }
            }

            if (failedItems.Count > 0)
            {
                Console.WriteLine();
                Console.WriteLine("The following items could not be converted. Please have them checked in and try again.");
                foreach (ComponentData component in failedItems)
                {
                    Console.WriteLine(component.Id + " :" + component.LocationInfo.WebDavUrl);
                }
            }
        }
    }
}

The Program Class

Now lets go back to the Program class’ Main method and put our code to work. We’ll allow the user to pass into the command line the arguments for the folder’s tcm uri, whether or not to perform this recursively, the schema uri of the components we want to change from, and the schema uri of the schema to change the components to. We’ll also display some a usage message when an incorrect # of arguments is supplied, a message showing the error if one pops up, and finally a message letting the user know that the script has completed.

static void Main(string[] args)
{
    if (args.Length < 4)
    {
        Console.WriteLine("Usage: Examples.ComponentSchemaChanger  <y/n for recursive>  ");
        Console.WriteLine("Example:");
        Console.WriteLine("Examples.ComponentSchemaChanger tcm:100-12345 y tcm:100-987-8 tcm:100-1234-8");
    }
    else
    {
        string folderUri = args[0];
        string recursive = args[1].ToLower();
        string schemaUriFrom = args[2];
        string schemaUriTo = args[3];

        try
        {
            SchemaChanger changer = new SchemaChanger();
            changer.ChangeSchemasForComponentsInFolder(folderUri, recursive.Equals("y"), schemaUriFrom, schemaUriTo);
        }
        catch (Exception ex)
        {
            Console.WriteLine("There was an error:");
            Console.WriteLine(ex);
        }
    }

    Console.WriteLine();
    Console.WriteLine(" press <ENTER> to continue");
    Console.ReadLine();
}

Building and Deploying

Right click the project and select “Build”. This will compile and build the project for you, putting the files you need in %Project Directory%\bin\Debug\ (or %Project Directory%\bin\Release\ if you are targeting Release). The 3 files that you’ll need are Examples.ComponentSchemaChanger.exe, Examples.ComponentSchemaChanger.exe.config (this is the file containing your app settings), and Tridion.ContentManager.CoreService.Client.dll.

Executing Our Script

Our script is setup to work either on the CMS Server or remotely with a simple configuration change. Remember that CoreServiceEndpoint application config setting that we added? When this setting is “wsHttp_2011″, you’ll be able to run your script remotely (as long as you have access to contact the CMS server from your location), and when this setting is “netTcp_2011″, you’ll be able to run locally on the CMS Server. You can actually run the wsHttp_2011 binding from the CMS Server, but the netTcp binding will perform faster for you.

Whether deployed locally or remotely, open up the command prompt, navigate to the folder you deployed to, and enter the following command:
Examples.ComponentSchemaChanger tcm:12-3456-2 y tcm:12-1000-8 tcm:12-1234

Sit back, and watch magic happen. :)

Again a Warning

Remember as mentioned, changing a component’s schema can lead you to lose data from fields if the field definitions are different. Before trying out the following code, make sure the components you are changing doesn’t contain any such fields that will be lost.

Stupid Mistakes: Tridion Event System and Async Subscriptions

It’s easy to make one of those simple and stupid mistakes when developing with Tridion.  You know, one of those mistakes that make you lose an hour or more to debugging.  One of those mistakes that, after realizing what you did wrong, you are extremely happy you found the issue, but at the same time still just a bit embarrassed that you missed it in the first place.  I thought I’d share one that I made over the weekend in hopes that perhaps if someone else accidently went down the wrong path they’d quickly be able to correct themselves.

Over the weekend I was working on some Event System code that worked with Multimedia Components.  I’ll spare most of the details, but the code had to do two main things: 1.) Get the binary data out of the Multimedia Component and 2.) Set a Metadata Field to a value if the field was empty.  Pretty easy right?  We’ve all done these things before and this kind of functionality should be smooth sailing.  Until its not…

Coded. Deployed. Created a new Multimedia Component.  Save and Closed.  And… wth? The Event Log showed the following error.

A database error occurred while executing Stored Procedure "EDA_ITEMS.GETBINARYCONTENT".
ORA-01403: no data found
ORA-06512: at "TRIDION_CM.EDA_ITEMS", line 4100
ORA-06512: at line 1

Looks like my code didn’t like my call to the GetByteArray() method… weird. This portion of the Event System was using the Processed event phase. Interestingly, switching it to Initiated made the error go away. Shouldn’t be a solution, but hey, it was working now. I’ll move on and figure out the root issue later.

Next I added code to set the Metadata Field when it was null. Coded. Deployed. Saved and Closed the modified MM component. And… wth again? The Event Log now showed the following error.

The item tcm:123-4567-16-v0 does not exist.

Another piece of code I’ve done countless times… why is it failing now?  Tridion, why have you forsaken me!?!

The Issue

After much debugging and even coming up with a workaround to get my code working, I looked up at the piece that was subscribing my event and immediately face palmed.

EventSystem.SubscribeAsync<Component, SaveEventArgs>(OnComponentSaveInitiated, EventPhases.Initiated);

Can you see the issue above? Yep, I had subscribed my event asynchronously, and those weird issues I was seeing was the price I paid for doing it incorrectly.  For those of you who are not familiar with the Tridion Event System, you should only subscribe events asynchronously via the SubscribeAsync method with a TransactionCommitted phase.

What had happened was, when I started on this Event Handler, I had a different set of requirements. I was originally going to use a TrasnactionCommitted phase and a Check In event, and started coding it, but switched to the Initiated phase with a Save event once I got the updated requirements. Unfortunately, I forgot to change the subscription method to not subscribe asynchronously.

EventSystem.Subscribe<Component, SaveEventArgs>(OnComponentSaveInitiated, EventPhases.Initiated);

Ah, all better now…

Another Clue…

Another clue that I had used the wrong subscription method should have been the errors themselves. Normally when an Exception is thrown in the Initiated or Processed phase, that error prevents the save, and the error message is displayed to the user. With my code, the component was saving just fine, and the error message was only getting logged and not communicated to the user.

Checking If An Item Is In Workflow With Core Service

During your quest of making custom applications using Tridion’s Core Service API, you may have come across the need to check whether or not an item is currently in a Workflow Process. Luckily, unlike some other tasks you may need to do with the Core Service API, this task is easily done by checking the WorkflowInfo property that is included in the PageData and ComponentData classes.

// Open a page in Core Service.
PageData page = client.Read(yourPageUri, new ReadOptions()) as PageData;

if (page.WorkflowInfo != null)
{
    // Ladies and gentlemen, WorkflowInfo property was not null, this page is in workflow
}
else
{
    // WorkflowInfo property is null, the page is not in workflow
}

Great, but now that you know that your item is in workflow, you’ll probably be wanting to grab some Workflow related items. The good news is, the WorkflowInfo class contains most of the data you’ll need to interact with the Workflow for that item. Here are some common things you’ll probably do with this property:

// Get the ProcessInstanceData from the WorkflowInfo property
ProcessInstanceData processInstance = 
    (ProcessInstanceData)client.Read(page.WorkflowInfo.ProcessInstance.IdRef, null);

// Get the ProcessDefinitionData from the process instnace
ProcessDefinitionData processDefinition =
    (ProcessDefinitionData)client.Read(processInstance.ProcessDefinition.IdRef, null);

// Getting the ActivityInstanceData from the WorkflowInfo property...
ActivityInstanceData activityInstance = 
    (ActivityInstanceData)client.Read(page.WorkflowInfo.ActivityInstance.IdRef, null);

// Get the ActivityDefinitionData from the activity instance
ActivityDefinitionData activityDefinition =
    (ActivityDefinitionData)client.Read(activityInstance.ActivityDefinition.IdRef, null);

// Note that if you only wanted to get the ActivityDefinition's Description, you can get that from the WorkflowInfo's ActivityDefinitionDescription property.
if (page.WorkflowInfo.ActivityDefinitionDescription.Equals(activityDefinition.Description))
{
    // true of course...
}

// Check how long its been since the activity was started...
if (page.WorkflowInfo.StartDate.HasValue)
{
    TimeSpan timeSinceStart = DateTime.Now - page.WorkflowInfo.StartDate.Value;
}

Other Useful Properties of WorkflowInfo

Here are the other properties of WorkflowInfo that wasn’t included in the samples above:

ActivityState: The enum value of the activity’s current state.
Assignee: The Link<TrusteeData> of the assignee.
CreationDate: The Nullable<DateTime> that the activity was created.
FinishDate: The Nullable<DateTime> that the previsou activity instance was finished.
Performer: The Link<UserData> of the activity’s performer.
PreviousMessage: The FinishMessage of the previous Activity.

Crocodoc .NET API Library Released

For those of you reading the title and asking, “What is this Crocodoc???”, Crocodoc is a tool that “converts Microsoft Office and PDF documents to HTML5 so your users can view and collaborate right in your web app.” What that means is, you can present your customers documents such as Word docs, PDFs, and Power Point presentations… embedded and viewable within your web pages. Long are the days where your customers must download your PDFs in order to view them.

That being said, my colleague and friend, Frank Taylor, needed to interact with the Crocodoc v2 REST API, but discovered Crocodoc was missing the .NET API. So after working together, we are now happy to announce that the Beta version of 1.0 for a .NET Wrapper has been released on its Google Code site. If you just want to jump straight into the action, you can download the binaries here. For those of you who might have worked with the Crocodoc Java or PHP API’s, you should feel comfortable with the .NET API.

Although the project’s Google Code site has plenty of samples (and there’s even an Examples project you can download and run as well), here’s a quick sample of its usage.

Crocodoc.ApiToken = "your-unique-api-token-right-here";
string sessionID;

try
{
    // Here, documentUUID is the uuid for the Document that you want to create a session for.
    sessionID = CrocodocSession.Create(documentUUID);
}
catch (CrocodocException ex)
{
    // Log or show error message if the Crocodoc server threw a web exception.
}

The above creates a unique session for a given document so that you can then embed it onto your webpage. The session allows you to set properties such as copyrighted, admin privies, or even allow the writing of annotations for your editors.

Here’s an example of uploading a new document to crocodoc.

try
{
    string newDocUUID = CrocodocDocument.Upload(@"C:\Documents\YourPDF.pdf");
}
catch (CrocodocException ex)
{

}

Your Help?

We have released it as version 1.0 Beta as we would like some of your help and feedback with the library. If you use Crocodoc and have been looking for a .NET API, try it out, and let us know any issues that you run into, or if you have any feature requests.

Quick Links

Crocodoc API Reference
Crocodoc .NET
Crocodoc .NET Binaries

Inherited Page Workflow Process Settings On Structure Groups

If you’ve worked with Tridion Workflows, you know that the process of assigning Component workflows differs greatly from the process of assigning Page workflows.  With components, you just add the Component Process to the Schema (and of course make sure you check the “Enable Workflow Process Associations in Shared Schemas and Structure Groups” from your Content Publication’s properties’s Workflow tab).  However, Page Processes are attached to Structure Groups.  And unfortunately Structure Groups do not inherit this setting, so if you wanted every location where pages get created to be affected, you’ll have to add this setting to each and every Structure Group.

Being inspired by Nuno’s post on inheriting metadata schemas on folders, I thought I’d write up a quick Event System example of how to mock inheritance of Page Processes on Structure Groups.

First lets write an event handler that will set the Associated Page Process of new Structure Groups to that of their parent Structure Group.

public WorkflowStructureGroupHandler()
{
    EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
}

private void SetNewStructureGroupProcessDefinition(StructureGroup sg, LoadEventArgs args, EventPhases phase)
{
    if (sg.Id.IsUriNull)
    {
        StructureGroup parentSG = sg.OrganizationalItem as StructureGroup;
        sg.PageProcess = parentSG.PageProcess;
    }
}

Fairly easy right? This takes care of the automatic setting of Page Processes for new Structure Groups, but what about existing Structure Groups? Normally by the time we start attaching process definitions, there could be a ton of existing Structure Groups already. What if we wanted to modify a parent Structure Group, and have all the children automatically updated? Lets add a Save event to the Structure Groups.

public WorkflowStructureGroupHandler()
{
    EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
    EventSystem.Subscribe<StructureGroup, SaveEventArgs>(SetInheritedStructureGroupProcessDefinitions, EventPhases.TransactionCommitted);
}

private void SetInheritedStructureGroupProcessDefinitions(StructureGroup sg, SaveEventArgs args, EventPhases phase)
{
    if (args.EventStack.Count() > 1)
    {
        return;
    }

    ProcessDefinition pageProcess = sg.PageProcess;

    OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(sg.Session);
    filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

    SetChildrenStructureGroups(sg, filter, pageProcess);
}

private void SetChildrenStructureGroups(StructureGroup sg, OrganizationalItemItemsFilter sgFilter, ProcessDefinition pageProcess)
{
    IEnumerable<RepositoryLocalObject> children = sg.GetItems(sgFilter);

    foreach (StructureGroup structureGroup in children)
    {
        if (structureGroup.PageProcess != pageProcess)
        {
            structureGroup.PageProcess = pageProcess;
            structureGroup.Save();
        }

        SetChildrenStructureGroups(structureGroup, sgFilter, pageProcess);
    }
}

And there you have it, modifying a parent Structure Group will not automatically set its children to have the same Associated Page Process, and creating a new SG will now automatically set its Page Process to that of its parent. The entire code will now look like:

using System.Collections.Generic;
using System.Linq;
using Tridion.ContentManager;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.Extensibility;
using Tridion.ContentManager.Extensibility.Events;
using Tridion.ContentManager.Workflow;

namespace Tahzoo.EventSystem.Samples
{
    [TcmExtension("WorkflowStructureGroupHandlerExtension")]
    public class WorkflowStructureGroupHandler : TcmExtension
    {
        public WorkflowStructureGroupHandler()
        {
            EventSystem.Subscribe<StructureGroup, LoadEventArgs>(SetNewStructureGroupProcessDefinition, EventPhases.Processed);
            EventSystem.Subscribe<StructureGroup, SaveEventArgs>(SetInheritedStructureGroupProcessDefinitions, EventPhases.TransactionCommitted);
        }

        private void SetNewStructureGroupProcessDefinition(StructureGroup sg, LoadEventArgs args, EventPhases phase)
        {
            if (sg.Id.IsUriNull)
            {
                StructureGroup parentSG = sg.OrganizationalItem as StructureGroup;
                sg.PageProcess = parentSG.PageProcess;
            }
        }

        private void SetInheritedStructureGroupProcessDefinitions(StructureGroup sg, SaveEventArgs args, EventPhases phase)
        {
            if (args.EventStack.Count() > 1)
            {
                return;
            }

            ProcessDefinition pageProcess = sg.PageProcess;

            OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(sg.Session);
            filter.ItemTypes = new List<ItemType> { ItemType.StructureGroup };

            SetChildrenStructureGroups(sg, filter, pageProcess);
        }

        private void SetChildrenStructureGroups(StructureGroup sg, OrganizationalItemItemsFilter sgFilter, ProcessDefinition pageProcess)
        {
            IEnumerable<RepositoryLocalObject> children = sg.GetItems(sgFilter);

            foreach (StructureGroup structureGroup in children)
            {
                if (structureGroup.PageProcess != pageProcess)
                {
                    structureGroup.PageProcess = pageProcess;
                    structureGroup.Save();
                }

                SetChildrenStructureGroups(structureGroup, sgFilter, pageProcess);
            }
        }
    }
}

The solution above is not perfect and there’s still some work that would need to go into this before I’d put it into production (unless of course your project is a one process fits all and everything needs to be affected the same). For example, you’ve configured your Structure Groups that have a combination of different processes and even some without. Edit the root Structure Group, and say good-bye to all of your work! An “Inherit from Parent” eXtension would be perfect here, so you can then tell Structure Groups to not inherit (and thus not change when one of the parent Structure Groups gets saved!) Unfortunately this story is coming to a close, and the GUI eXtension’s story is a tale for another day.

Razor Helpers and Functions

Now that the latest version of the Razor Mediator for Tridion (v 1.2) allows you to import, I thought perhaps it would be helpful if I go over the Razor syntax for creating Razor Helper functions, or just adding normal functions in general.  Using these in your Razor Templates greatly enhances the functionality that you can perform in your templates.  On my latest project at a client, we developed some pretty advanced templates, and I found myself thinking (and shuddering) of how it would have to have been done if done using Dreamweaver templating.

Normal Functions

You can declare functions in your Razor code and call it just like you were calling any of the built in functions using @functions. Remember, you can have as many @functions sections as you want in your templates, you are not just limited to one.

@functions {
    public string HelloWorld(string name) {
        return "Hello " + name;
    }
}

The above defines a function called “HelloWorld” that you can start using in your template code (or callable in other functions).

<strong>@HelloWorld("John")</strong>

Helper Functions

Helper Functions are also callable via your template code, however Helper Functions allows for Razor syntax and HTML markup from within the function itself.

@helper HelloWorld(string name) {
    <div>Hello <em>@name</em>!</div>
}

The Helper Function would then work exactly the same as the non-Helper Function created above.

<strong>@HelloWorld("John")</strong>

You can mix and match your functions as well.  Following is just a couple more quick examples.

@functions {
    public string GetExtraAttributes(Models.ComponentModel component) {
        string attributes = String.Empty;
        if (component.Metadata.KeyValuePairs != null) {
            foreach (var values in component.Metadata.KeyValuePairs) {
                attributes += String.Format("{0}="{1}"", values.Key, values.Value);
            }
        }
        return attributes;
    }
}

@helper RenderComponentLink(Models.ComponentModel component) {
    <a href="@component.ID" @GetExtraAttributes(component)>@component.Title</a>
}

@helper WrapInParagraphTags(string input) {
    if (input != null && !input.StartsWith("<p")) {
        <p>@input</p>
    } else {
        @input
    }
}

Tridion PublishEngine – Of Transactions and Publish Information

Today I thought I would give some examples of querying publish transactions (publishing queue) as well as getting publish information from specific items, like seeing which Publication Targets a page has been published to and when, or just seeing if an item has been published in general. This post is inspired by a recent question asked in the forums, but I have noticed it come up from time to time. Luckily this task is easy using the TOM.NET API and the static PublishEngine class.

IsPublished

One task you might need to attempt in your Tridion development day to day activities is to check to see if a given item has been published, or published to a particular Publication Target.  PublishEngine contains the following overloaded methods.

bool IsPublished(IdentifiableObject item)
bool IsPublished(IdentifiableObject item, PublicationTarget publicationTarget)
bool IsPublished(IdentifiableObject item, PublicationTarget publicationTarget, bool isPublishedInContext)

The first IsPublished method is useful if you just want to see if an item is published, regardless of which Publication Target it has been published to.

if (PublishEngine.IsPublished(page))
{
    // This page has been published. Do cool stuff here.
}

The second IsPublished method will return true only if the item has been published to the Publication Target passed in the 2nd argument. If the 2nd argument is null, then it’ll act just like the first method and return true if the item has been published to any Publication Target.

if (PublishEngine.IsPublished(page, pubTarget))
{
    // This page has been published to a specific publication target. Do cool stuff here.
}

The third method allows for even finer control. I haven’t personally played with this one as of yet, but the documentation for the isPublishedInContext argument states “Indicates if state should be returned regardless of the context Publication. true only check if item is published in the context Publication; otherwise, false.” I’m assuming this means that, if this argument is set to true, the method will only pass if the item is published to a particular Publication Target, and only if the item is published in it’s own context Publication.

GetPublishInfo

What if you need to get more information about an item? Like, what if you not only wanted to see what Publication Targets it was published to, but at what time too? That’s where the following method comes in hand.

foreach (PublishInfo info in PublishEngine.GetPublishInfo(page))
{
    Console.WriteLine("Published To: " + info.PublicationTarget.Title);
    Console.WriteLine("Published At: " + info.PublishedAt); // The time the page was published
    Console.WriteLine("Published By: " + info.PublishedBy.Title); // The user who published
    Console.WriteLine("Rendered With: " + info.RenderedWith.Title); // The title of the template used
}

The above will loop through each Publication Target that the item has been published to and give you some useful information about the publishing.

GetPublishTransactions

And what if you actually need to check the publishing queue to see if an item has recently been published in the past hour? The PublishEngine allows you to also query the publish transactions.

XmlElement GetListPublishTransactions(PublishTransactionsFilter filter)
IEnumerable<PublishTransaction> GetPublishTransactions(PublishTransactionsFilter filter)

Note that the above methods will throw an AccessDeniedException if the user is not a System Administrator or have PublishManagement rights in any publication. That means if your code relies on always needing to be able to check the transactions, regardless of rights, you’ll need to impersonate.

Session session = new Session("DOMAIN\username"); // Impersonate System Admin or at minimum user with PublishManagement rights.
 
PublishTransactionsFilter filter = new PublishTransactionsFilter(session);
filter.StartDate = DateTime.Now.AddHours(-1); // Add some criteria for when the publishing was started.
filter.PublishTransactionState = PublishTransactionState.Success;

IEnumerable<PublishTransaction> transactions = PublishEngine.GetPublishTransactions(filter);

foreach (PublishTransaction transaction in transactions)
{
    // Check cool stuff with the transaction, like the transaction.Items property.
}

The above will loop through all the successful transactions that has happened in the past hour. Notice the PublishTransactionState property… it allows you to only filter based on one state. But what if you need to grab anything that’s not Success or Failed perhaps, or any other combination of states? You’ll have to grab the items and filter programatically.

PublishTransactionsFilter filter = new PublishTransactionsFilter(session);
filter.StateDate = DateTime.Now.AddHours(-1);

IEnumerable<PublishTransaction> transactions = PublishEngine.GetPublishTransactions(filter)
    .Where(t => t.State != PublishTransactionState.Success && t.State != PublishTransactionState.Failed);

foreach (PublishTransaction transaction in transactions)
{
    // Do cool stuff with transactions that are not Success or Failed
}

And with that I leave. Happy developing everyone!

Razor Mediator Version 1.1 Released

I am happy to announce that the Razor Mediator version 1.1 has been submitted to SDL to be updated on SDL Tridion World. In the meantime, you can grab the v1.1 installer at its Google Code project site. So what exactly has been updated since version 1.0?

KeywordModel

Just like the other Tridion items that have been wrapped in Model classes, the Keyword class has finally been wrapped as well.  This means you can easily access a Keyword’s metadata just like the other objects as well.

@Fields.SomeKeywordField.Metadata.SomeMetaField

All fields that would normally return a KeywordField will now return a KeywordModel (or List<KeywordModel> for multi-valued fields).  The @Models.GetKeyword(string) method also returns a KeywordModel.

GetComponentPresentationsBySchema(string schemaName)

A new utility method has been added to the base template class to allow you to easily grab all Component Presentations filtered by the component’s schema title.

@foreach (var cp in GetComponentPresentationsBySchema("Article Schema")) {
    <text>@cp.RenderComponentPresentation()</text>
}

DynamicPackage

This feature was listed in version 1.0 but wasn’t actually used.  All calls to the @Package property was really just returning the Tridion Package instance.  So, what exactly can you do with DynamicPackage?  You can easily grab package items and parameter values in your templates using dot notation.  For backwards compatibility, GetByName(string name), GetByType(ContentType type), and GetValue(string name).  The following would all work assuming there was either a package item of a string type named “ItemName” or a parameter field with xml name of “ItemName”.

@Package.ItemName
@Package.GetByName("ItemName").GetAsString()
@Package.GetValue("ItemName")

And some fixes…

Besides the several new features, some fixes were also added.  For a complete list of fixes, feel free to check out the Change Log page.  If you come across more issues, please let me know so that I can get them fixed ASAP. And if you have some cool ideas for possible features that you want to see in the Razor Mediator, shoot them over as well.

What’s Next?

I’m planning on putting an easy way to extend your Razor templates using reusable helper methods and functions.  So look forward to version 1.2 coming out sometime in the hopefully near future, as well as another blog post to show you examples of creating helper methods in your current templates!