Tuesday, January 15, 2013

HOWTO: Add semantic markup to automatically published content in Sitefinity

One of my favorite features in Sitefinity 5 is the "Alternative publishing system". Basically, you get a number of inbound and outbound pipes and you can miss and match them in order to publish content in or out of your CMS via different channels. This is extremely convenient and extensible framework, which can be of great help to accomplish not so common scenarios. Just a few examples of what can be achieved - you can easily expose all your blog posts as a RSS feed, you can take a RSS feed and publish the items in it as blog posts, news items, tweet it, etc. What's even better - you can define your own custom inbound and outbound pipes to extend this system and suit it to your needs (if only there were enough documentation about this).

The Scenario

What we need for one of our projects is to automatically publish content from external blogging frameworks into our Sitefinity project. This is easily accomplish with taking RSS feeds from the external platforms and setting them as inbound pipe and publishing them as blog posts. This works greatly out of the box. The problem comes when the search engines index our automatically published content - since this content will be exact duplicate, we have to decorate it the proper way so our SEO rank doesn't suffer. We have to set the following markup in our page, to designate the original source of this content:
<link rel="canonical" href="[original url]" />

Extending the Blogs module

First, we need a new field to keep the source of a blog post. Every post that is automatically published will have this field filled with the url of the original blog post. The rest will have just an empty field. This is easy to be done through the administration: Go to Blogs module -> Click on a random blog -> "Custom fields for posts" -> Click on "Add a field". Choose appropriate name (let's say CanonicalLink) for the field and set it to a short text type. Done.

After that we need to write some logic that adds the suitable markup when this field is not empty. To achieve this, the easiest way I found is to derive from "Telerik.Sitefinity.Modules.Blogs.Web.UI.Public.DetailPostView" class, override the InitializeControls method and implement the logic I need there.

Finally, we need to set this modified class as a detail view for blog posts - this is done again through the administration: Administration -> Settings -> Advanced -> Blogs -> Controls -> BlogPostsFrontend -> Views -> DetailBlogPostsFrontend. There is a field "ViewType" here and you should set the fully qualified typename of the modified class. Hitting save will modify some config files, so they should be checked outand the app pool user should have permission to modify them.

So far so good, if we have the field filled in - the proper markup will be rendered. We need to fill the field now.

Extending the Alternative publishing system

As I mentioned the backbone of this system are inbound pipes and outbound pipes. These are basically the input and the output of the system. For this task, we need to extend the default outbound pipes for Sitefinity content and make it to fill the custom field we already created. So, we derive from the Telerik.Sitefinity.Publishing.Pipes.ContentOutboundPipe :

As you can see, I've override the SetPropertiesThroughPropertyDescriptor method to set the custom field I need. Using reflection (no other way unfortunately), I look for a property called "Link" in the wrapperObj - this is the object Sitefinity provides to the outbound pipe, so the pipe can produce the Sitefinity content.

There is another static method used to register the pipe. Here comes the bad news - I couldn't find a way to replace the default outbound pipe with the modified one, so I had to add a new one. This might be incovenient for the end user and leads to misunderstanding sometimes (probably hiding the default one from the UI with css is an option). Everything I tried with removing the default one or replacing it - leads to runtime errors. So, here is how to add a new outbound pipe - subscribe for the initialized event that Sitefinity exposes and call the static method that is already provided in the outbound pipe:

Well, that's all - after you build and restart you will get your new outbound pipe as an option in the dialog:
 As I said, you will have to use the Enhanced Sitefinity content pipe instead of the default one - Sitefinity content. It will act as default one in any other way, but will fill the original link in the custom field we added. I hope this will help someone. Special thanks to Boyan Barnev for helping me with this one, I wouldn't do it without his help :)