Content Bloom Global Summit 2014

Content Bloom Sign

Our sign outside of our Halifax location

This past week has been such an extraordinary week with the rest of the Bloomers at the Content Bloom Global Summit, our annual company event. While last year they held the event in the charming Belgium spot, this year was hosted at the beautiful Halifax location in Nova Scotia. The events were a week long experience, arriving on Sunday and leaving the following Sunday, filled with socializing, team bonding, training, presentations, workshops, demos, and just great fun. Every person got to give a couple of presentations that they specialize in, and the group itself was very active in participating and asking questions. And with a group like Content Bloom, many great questions and conversations were had for each presentation!

Group photo at Peggy's Cove!

Group photo at Peggy’s Cove!

This year’s topics included Tridion Templating, DD4T, Media Manager, Advanced Tridion Architectures, TCDL, Event System, Core Service API Usage, Workflow, and other cool Tridion items. Other presentations included advanced .NET and Java development, Java for .NET guys, front end development, and overviews of the company and its processes in general.

Rob himself talking about Tridion's TCDL tags at the Halifax office.

Rob himself talking about Tridion’s TCDL tags at the Halifax office.

An outdoor presentation by Primmer on Tridion Workflow after a rocky coastline hike! He was threatened to be thrown in if the presentation sucked.

An outdoor presentation by Primmer on Tridion Workflow after a rocky coastline hike! He was threatened to be thrown in if the presentation sucked.

But of course the week wasn’t just about learning, it was getting to know one another and seeing each other face to face. Our evenings were spent going on runs through hilly forests, going on hikes, diving into lakes and freezing cold ocean waters, exploring the local sites and scenery, beach lounging, barbecuing at Nick’s house, and of course being the group that we are, much much pub crawling! There was also some climbing up something they call The Wave, a concrete sculpture that just beckons you to reach the top of it (even though there are signs saying not to climb). I’m sure it was a hilarious sight for any passerby, a large group in the middle of a slightly drizzly night running up a concrete slope and nearly breaking their necks as they slipped and came tumbling back down. I also attempted to skateboard (or long board?) for the first time ever, which resulted in me doing the splits and learning how not to stop yourself while speeding up down a hill.

Some much needed relaxation on a beach after an intense week of presentations and training!

Some much needed relaxation on a beach after an intense week of presentations and training!

Much thanks to John, Nick, Miles and Oksana for organizing and running the event! It was an incredible experience of a week and an honor to be among such a talented and fun group of peers!

Our last night of the summit at "Your Father's Moustache"

Our last night of the summit at “Your Father’s Moustache”

Searching and Modifying Output In Tridion Template Building Blocks With HtmlAgilityPack

In your Tridion career, you’ve probably written countless of C# Template Building Blocks. You’ve probably rolled your own custom Link Resolver, your own “Add or Extract Binaries From…”, your own cleanup templates… dozens of Building Blocks where the goal was to search for specific html patterns and attributes or modify the output in some way. And, if you’re like me, you’ve probably had to write numerous regex expressions to accomplish your tasks. I’ll admit that I’m no regex ninja or anything, its usually through trial and error that I get my regular expressions working correctly. Recently however, I attempted to write an expression that handled nested elements that could go any number of levels deep, with possibility to have several different variations of the pattern that I was looking for. My regex skills was just not good enough, and I thought for sure there must be a better way to parse the html output string of these patterns.

The search was short, but I found exactly what I was looking for: Html Agility Pack

Using HtmlAgilityPack in your Tridion Template Building Block is simple… just reference the DLL, and make sure you install the DLL into the GAC on the CMS and Publishing servers.

Now that we are set up and ready to run, let’s go ahead and write some code that will take the output from the package, and load it into an HtmlDocument.  Your code should look something like the following:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Tridion.ContentManager.Templating;
using Tridion.ContentManager.Templating.Assembly;
using HtmlAgilityPack;

namespace CodedWeapon.Samples
{
    [TcmTemplateTitle("HtmlAgilityPack Tester")]
    public class HtmlAgilityPackTester : ITemplate
    {
        private TemplatingLogger _logger = null;

	protected TemplatingLogger Logger
	{
		get
		{
			if (_logger == null) 
                            _logger = TemplatingLogger.GetLogger(this.GetType());

			return _logger;
		}
	}

        public override void Transform(Engine engine, Package package)
        {
            Item outputItem = package.GetByName(Package.OutputName);
            string outputString = outputItem.GetAsString();

            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(outputString);
        }
    }
}

If you’re familiar with XmlDocument, then HtmlDocument should not look so strange to you. All we are doing in the above is grabbing the output, and passing the output (as a string) into our HtmlDocument instance. Simple enough right? And now for the fun… seeing how simple it is to use this library to get the exact elements that we are looking for. Since a lot of the time we work with links, I’ll show some examples of grabbing some anchor tags.

Grabbing Every Anchor

Lets say we want to grab every anchor element present on the output:

HtmlNodeCollection nodes = doc.DocumentNode.Descendants("a");

You can loop over each of the nodes and perform any necessary operations that you want:

foreach (var node in nodes)
{
    Logger.Debug("Found node with url: " + anchor.Attributes["href"].Value); 
}

Just like with XmlDocument, you can also use XPath:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a");

Querying for Specific Elements

You can use XPath to search for the specific elements that you are looking for. For example, if we wanted to match all links with a specific url:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[@href=\"http://www.example.com\"]");

Or what if we wanted to grab every link that actually contains a title attribute:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[@title]");

Or better yet… what if we want check any node that does not contain the title attribute at all?

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[not(@title)]")

If you’re not a fan of XPath, then you’ll be happy to know that you can also use LINQ like in this following example where we are looking for any link that has an href attribute of “#”:

var nodes = doc.DocumentNode.Descendants()
    .Where(n => n.Attributes["href"] != null && n.Attributes["href"].Value.Equals("#"));

Modifying Output

Normally when we’re searching for elements it’s because we may need to modify the markup in someway, either by adding attributes, removing elements, etc. In the following example, we are adding a custom attribute onto our anchors that contain a value of “#” in the href attribute:

foreach (var node in doc.DocumentNode.SelectNodes("//a[@href=\"#\"]"))
{
    node.Attributes.Add("data-my-custom-attribute", "true");
}

But what if we have a more complicated requirement where we need to strip a <tcdl:ComponentPresentation> tag (but ensure that we keep the inside markup). One may be tempted to try the following:

foreach (var node in doc.DocumentNode.Descendants("tcdl:ComponentPresentation"))
{
    node.ParentNode.RemoveChild(node, true); // this strips the tcdl tag while preserving the children... but its modifying the collection...
}

package.Remove(outputItem);
outputItem.SetAsString(doc.DocumentNode.OuterHtml);
package.PushItem(Package.OutputName, outputItem);

However, while running the above, you’ll get an error that says Collection was modified; enumeration operation may not execute. This is because you cannot modify the collection (add/remove) while enumerating it. But, we can keep a record of the nodes and operate on them after:

List nodesToChange = new List();
foreach (var node in doc.DocumentNode.Descendants("tcdl:ComponentPresentation"))
{
    nodesToChange.Add(node);
}

// Now we strip the wrapping tags...
foreach (var node in nodesToChange)
{
    node.ParentNode.RemoveChild(node, true);
}

package.Remove(outputItem);
outputItem.SetAsString(doc.DocumentNode.OuterHtml);
package.PushItem(Package.OutputName, outputItem);

Hopefully this will end the regex blues out there for anyone who may be struggling!

Reloading/Refreshing Lists in the Shortcuts Area

A recent question on stack exchange regarding the ability to refresh or reload the favorites section using the Anguilla API has sparked this post, where I thought I would expand a little bit on the answer.  What the asker was doing was modifying the favorites (in this case the names), but needed the favorites section to be reloaded in order to reflect these changes. The shortcuts area is within an iFrame, so my first thought was to use some regular JavaScript to reload the iFrame itself via:

$('#FavoritesTree iframe').contentWindow.location.reload(true);

But as the asker of the question realized, this actually causes the entire Tridion GUI to be loaded into the shortcuts area!  Definitely not what we want to do!

Luckily for us, Anguilla has a way for us to unload the lists which will automatically cause them to reload. If we want to refresh all of the lists inside of the shortcuts area, we can do the folllowing:

$models.getItem("cme:userfavs").unloadLists();

So what’s going on in the above statement? $models.getItem("cme:userfavs") retrieves the shortcuts area which is represented by the type Tridion.Cme.Model.FavoritesFolder. This object contains the method unloadLists() which does the magic for us. But suppose you only want to refresh a specific list in the shortcuts area?

$models.getItem("cme:shortcuts").unloadLists();
$models.getItem("cme:custompgs").unloadLists();
$models.getItem("cme:usrtsks").unloadLists();

Again, with each of these items you can just call the unloadLists() method to refresh that specific list. You can even target the child lists of these lists… for example, $models.getItem("cme:chklst"); which would get the Checked-out Items from the My Tasks. If you’re trying to figure out how you can get the identifier to pass to $models.getItem(), a good place to look is the address bar after clicking on the list you wish to target with your code. For example, after clicking on the “Checked-out Items”, you should see “Dashboard.aspx#locationId=cme:chklst” at the end of the address bar. The value of the locationId is what you want to use.

Happy coding everyone!

Back From The Dead

Hello dear friends and fellow Tridionaughts! Too long has it been since my last post, update, and involvement in my beloved Tridion community. But hopefully this post will be the first of many more to come as I crawl forth back from the dead, not as a flesh and brain eating zombie (but how cool would that be right? well… maybe not…), but optimistically as a once again involved blogger and Tridionaught. A lot has happened in my life since my disappearance, but most important of which was the birth of my beautiful daughter Lillian on April 19th. My army of mini-me developers will soon be a reality!

And a farewell to colleagues…

It’s with a heavy heart that I bid farewell to my colleagues and friends at Tahzoo as I travel wide eyed down the road to new and exciting adventures. Leaving behind something that I was a part of for four years was no easy decision, for it was a joy and pleasure to work along side my colleagues who’ve became great friends to me.

And a hello to the new…

With excitement I now say hello and greetings to my new colleagues and family at Content Bloom, a company started and created for similar beliefs and values that I hold myself by fellow Tridionaughts and MVP’s John Winter and Nickoli Roussakov. I’ve had the pleasure of getting to know and talk to John and Nick, as well as some of the other Bloomers, and I am thrilled to be working along side such fellow tech enthusiasts.

I hope to fuel the fires of creativity and innovation with such a company that has the passion and commitment to both technology and the client. Will that mean more blog posts for the Tridion community? Or maybe the birth of Razor Mediator 2.0? Maybe even finishing up some of the blog posts that I’ve started and never got around to wrapping up and publishing.

Or…?

Client Templating For Tridion – Part I

As mentioned in my previous post, the MVP’s actually got down to grind out some work and code during the MVP Retreat. Nuno gives a nice overview of the open source projects in his post. I joined the Client Side Templating team, which was led by Will Price and also included Angel Puntero and Mihai Cădariu. The following post(s) will give some context to the details of that project.

Just show me the code…

If you don’t want to be bored by reading and just want to check out the project itself, feel free to check out the project site’s wiki pages and code.

Current Templating Models

Publish Time Rendering

Currently Tridion works out of the box with a templating model that gets resolved at publish time. If you have used DWT or the Razor Mediator, you have been using this such model. This is great for publishing static content, but what happens when you want to start diving into dynamic content? Sure, you can publish dynamic component presentations. With DCP’s, your presentations are still being resolved at publish time, but you can pull the content in dynamically on your pages via the Content Delivery API.

Dynamic Rendering or Request Time Rendering

Even with using the DCP’s, your development process can still end up feeling very inflexible and hacky at times. That’s where such frameworks such as CWA or DD4T come to the rescue. These awesome frameworks offer an MVC application side templating approach to your projects. Your templates actually live on the application server, and are rendered dynamically at request time. With CWA, your page views are dynamic while your component presentations are still rendered at publish time, and then your views pull the data and presentations in. With DD4T, templating is completely removed from the CMS side of things, and both the page and component views are handled by the application. These frameworks offer a much larger flexability in the way you can architect your projects, and even some added bonuses. For example, with DD4T (and using ASP.NET MVC), you can do your template development inside of Visual Studio using Razor and get the best out of the Intellisense tools. There’s no better feeling than getting drop down help and documentation as you code along with immediate errors and warnings without having to compile the project.

But Why Client Side Templating?

You’ve probably seen a lot of client side frameworks becoming extremely popular as of late. That’s because the benefits of letting the client’s browser handle some (or sometimes all) of the templating for you has numerous benefits. The number one benefit of course is the reduction costs. If you think about it, besides images and other binaries of course, the bulk of the size of your requests isn’t necessarily the data you are serving, but rather the markup itself. Most of your pages probably have the exact same markup, and only the data its relaying is different. Say if you had a 1000 Product pages. If your visitor visits each of those pages, your server has had to render the exact same markup for that page, and serve it, 1000 times, even though its pretty much the exact same page. But what if instead you only loaded the markup in the form of a template once, cache it, and for each of the 1000 pages only send your customer the raw json data needed for the page? You’ve now not only saved on bandwidth costs, you’ve saved on processing that your server would normally have to do for template rendering, and you’ve most likely decreased your web applications load time. Awesome, right?

CT4T

And that’s where CT4T comes into the picture. It’s a set of tools and API’s to easily add client side templating to your projects. The best part is, because its just JavaScript, its flexible enough to allow you to mix and match the different publishing models to meet your project needs. You can continue using your DD4T or CWA project if you wish to and just augment it with some client side goodness. Or you can mix a bit of old fashioned static pages published from Tridion with client side templating. Or you could be daring and create a completely new single page web application. The choices are yours.

To Be Continued…

In my next post I will discuss a bit about the architecture and decisions that were made so far. Remember, this is an open source community project, and if you feel like this is something that interests you, or better yet something that your client would love to have, I encourage you to contribute and join in. The CT4T project is still in its infancy and has a long way to go, and you have the chance to participate to create something great.

Join Us!

2013 SDL Tridion MVP Retreat

It’s been a very interesting past few days here at the SDL Tridion MVP Retreat, this year being held at the wonderful location of Óbidos, Portugal. Upon stepping out of the bus with the other MVP’s, it was like taking a step back into time into a medieval like setting. We walked from the bus up a narrow cobblestone pathway that lead to our hotel, the Hotel Real D’Óbidos, where we could see the walls of the castle and the fortified village area.

An Obidos View

After checking in and having some breakfast, it was down to business for the MVP’s. We met up in an office room out looking the hotel’s swimming pool and spectacular view of the lower village area. Office View Nuno started off with a great speech about the MVP program and the meaning of being an MVP, and also told us about some upcoming awesome Tridion features. Then we discussed the projects that the MVP’s would be working on that were voted by the community. The projects that we would be doing were Custom Content Views (aka the Tridion Field Builder Injector), Client Side Templating (aka CT4T), Testing and Automation (aka Tridion Implementation Testing System), and Responsible Web Design. After discussing what each of the projects were, each of us decided which project that most interested us, and then broke for lunch. Once lunch was finished, we eagerly split into our groups and started discussions on what technologies that we wanted to use, the architecture of the projects, and the minimal amount that we wanted to have done for the demos that we would be doing first thing Saturday morning. I had joined the Client Side Templating group, led by Will Price, along with Angel Puntero and Mihai Cădariu (the details of this project will be in another blog post though).

Once the work day was over, we were given a break to go back to the hotel rooms and then to meet back out front to go to our very first event. We were greeted by the jingling of bells and the trotting of hooves as 3 horse-pulled carriages pulled up to the hotel. Our Ride to the CastleWe climbed into the carriages and rode off down the cobblestone roads to our event… dinner at the castle. We felt like celebrities as tourist flashed their cameras at us as we trotted along the narrow roads with a line of cars behind us. At the castle, we had a welcoming drink(s) before the dinner, where the servers kept bringing us drink after drink before taking us up to our table where we enjoyed a delicious 3 course meal paired with wine.Night View From Obidos Castle The night ended at a local Ginjinha bar (followed of course by a walk back to the hotel in deep concentration so not to fall on my face over the stony pathways).

Day Two – Friday

Grind day. The teams crunched furiously at their keyboards to get ready for the demos that were to be given the next day. Each team also gave a quick presentation about where they were going with their projects to get early feedback and suggestions from the other teams. MVP's At WorkAt the end of the work day, 4 new open source Tridion projects awaited the Tridion Community to start taking a peek at. And, after a day of hard work, came a night of hard play. A grand bbq awaited us on the hotel grounds that evening, filled with great food, an endless supply of alcohol, games of Foosball, pool and darts, a live performance by Quirijn Slings and other MVP’s, a face caking… and… did I mention the endless supply of alcohol?

Day Three – Saturday

Saturday morning the teams did a splendid job of presenting their projects. It’s amazing the amount of work that was produced in such a short term period by these talented folks and colleagues. The presentations lasted until noon, where we broke for lunch and then went off to our events for the rest of the day. An Obidos Guided TourIt began with a guided tour around the walled village and we actually had a woman who knew her stories pretty actually well. When the tour ended at the castle itself, we jumped into several jeeps and trucks where we did a bit of off-roading to the next event – a tour through FRUTÓBIDOS, a Ginjinha brewery. A brewery tour...After the brewery, it was more off-roading through some rough spots (the jeep that I was in got stuck in one of the spots on a sandy hill) and some air time in the back of the jeep (thankfully I never hit the ceiling), with several stops along the way for smaller events such as rope top spinning (I don’t quite remember the name of it), archery, and a group photo in front of a lagoon on a truck.An off-roading lagoon stop... The off-roading ended at a very eco-friendly restaurant where they grow their own food, and the structures themselves are made out of recycled materials. The food and drinks was extraordinary as the MVP crew stood around a fire telling stories of past events and Tridion projects (and more singing) until it was time to go back to the hotel.Restaurant camp fire

Thank You’s

I wanted to say a personal thank you to SDL Tridion for the MVP program. The program is about community and sharing, and winning the MVP is so much more than just some title that you can slap on a resume or dazzle a client with. It’s about being part of that community, and being a part of so much more. And the MVP Retreat is something that shows that Tridion really cares about the MVP program and wants to do something special and nice for its MVP’s. This retreat was an experience that I will remember and look fondly back on for the rest of my life. So thank you, for giving me an opportunity to have this experience. I encourage all of my fellow Tridion colleagues to shoot for winning this program if they have not already, as it is definitely well worth the effort and I promise it will be an experience you too will never forget.

And Cheers Actually

And for my fellow MVP’s and Tridionaughts who were actually out on the retreat with me, it was actually quite nice to finally get the chance to put faces and personalities behind the words of your blog posts and your answers on stackoverflow. I mentioned to some of you that I am actually quite shy and I was a bit intimidated to be out there with such an intelligent lot such as yourselves, but you actually made me feel quite comfortable and right at home, and I actually had a much greater time than I actually thought I would’ve or could’ve had. I traveled there with you as fellow community sharers, and I left there with you actually considered as friends. And for those of you who know what I am actually talking about and was actually there, I actually hope that I’m lucky enough to get the chance to win once again next year, so that I can actually be there again just to hear the stories that actually happened after you have read this last paragraph. So salud and cheers my friends. Til next time actually.

Razor Mediator Version 1.3.3 Released – Operation Memory Leak Blues

I’ve been slacking in my blog writings as of lately, but hey, I have a pretty good reason for that.  As of May 26th, I am married to my best friend and love of my life, Erin Churchill (now Erin Klock of course).  With work, wedding planning, and a honeymoon trip, finding time to write and to work on side projects was hard.  With that said though, I am pleased to announce the latest release of the Razor Mediator (which as usual can be found at its Google Code Site).

Before I go into what this release fixes (as if you couldn’t tell by the title), I would like to give a special thanks to Dominic Cronin, who not only took charge of this release while I was in Hawaii, but also dug in and found the fix as well (which in turn made my wife happy as I wasn’t under some palm tree working on my laptop, which I shamefully admit to bringing). Also a thanks to Andreas Johansson for testing and optimizing the code a bit more, and to Robert Curlette for also thoroughly testing the updates. If you are one of the ones who’s issue is resolved by this release, you should give a shout out to these guys.

This version solves a memory leak issue that some projects have been experiencing on the publishing server. It removes the expiring cache times that previous versions had, so the only times that your templates are recompiled is when A.) you update them or B.) you update one of the using imports (which now uses the Where Used functionality added in version 1.3 to track down where the imports are used). The latter will only work when you have enabled the Where Used capabilities in the configuration (enabled by default). If for some reason you are not using this feature, then all templates are removed from cache when you make any razor template update. Also a major culprit was an event delegate that Dominic tracked down that wasn’t getting detached. As an extra benefit of this fix, you should now see publishing times increased as well.

Again, many thanks to these guys for their help, and to everyone who has so far given feedback, reported issues, and made suggestions. The Razor Mediator wouldn’t have gotten this far without you guys.

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.