Thursday, March 11, 2010

Publish to pre-production web database

Update 3/11/2010: Correction » the last parameter of the ExtendedPublishOptions constructor should be “false”.  Please see below for details.

In my experience with enterprise level implementations, there is often a need for a separate “stage environment” for final content preview or a pre-production phase of workflow.

It is also often the case that the environmental limitations or restrictions make the content delivery part of the authoring environment suitable for such purpose. For example, your e-commerce infrastructure could not be available from the authoring instance. One of the possible ways to approach such a requirement is to configure a stand-alone staging instance of Sitecore which will be used for content delivery instance and is commonly configured exactly like one of the servers in the web farm. There is one exception though – the database that is used for content delivery is generally different from the one that is used in production content delivery.

With this approach, now the question is how to plug in workflow in a way that content could go via this “stage” environment and then only upon final approval will be able to get to “production”.

Now there is a problem that you cannot get get items published until they are in final workflow state. You could have two workflow states “Staged” and “Published”, both marked as final and with auto publish actions connected.
I don’t like this approach since it goes against the workflow nature – only one state should be marked as final.

Alternatively, you can plug in a “copy item” workflow action to the “Staged” workflow state where you can programmatically copy an item from master database to stage web.

While this approach does seem legitimate, it works around the need of publishing thus any additional processing you may be having in publish(item) pipeline would not work.

I looked into the option of having publishing process ignore workflow when publishing to stage. While this seemed like a dangerous path to go, I have soon discovered that the PublishHelper’s GetVersionToPublish method already accepts this notion of passing a “requireapproval” flag to the underlying publishing mechanism. Since it is always passes “true”, I started looking into ways to make this flag dynamic and figured that setting it on the level of PublishOptions could be a good idea. For example, from the code of my workflow action I would be able to define whether I want to have workflow respected or not.

Here is what you will need to do to make it work.

First, override the default PipelinePublishProvider and plug it into the web.config:

<publishManager defaultProvider="default" enabled="true">
        <clear />
         <!--<add name="default" type="Sitecore.Publishing.PipelinePublishProvider, Sitecore.Kernel" />-->
         <add name="default" type="SCUSAINC.Publishing.ExtendedPublishProvider, SCUSAINC.Publishing" />

Secondly, you will need to override the CreatePublishHelper class:

public class ExtendedPublishProvider : PipelinePublishProvider
        public override PublishHelper CreatePublishHelper(PublishOptions options)
            Assert.ArgumentNotNull(options, "options");
            if (options is ExtendedPublishOptions)
                return new ExtendedPublishHelper(options as ExtendedPublishOptions);

            return new PublishHelper(options);

After that, introduce two more classes – ExtendedPublishHelper and ExtendedPublishOptions:

public class ExtendedPublishHelper : PublishHelper
        private readonly ExtendedPublishOptions _options;

        public ExtendedPublishHelper(ExtendedPublishOptions options)
            : base(options)
            _options = options;

        public override Item GetVersionToPublish(Item sourceItem)
            Assert.ArgumentNotNull(sourceItem, "sourceItem");
            if (Options is ExtendedPublishOptions)
                return sourceItem.Publishing.GetValidVersion(Options.PublishDate, _options.RequireApproval);

            return sourceItem.Publishing.GetValidVersion(Options.PublishDate, true);

public class ExtendedPublishOptions : PublishOptions
        public ExtendedPublishOptions(Database sourceDatabase, Database targetDatabase, PublishMode mode, Language language, DateTime publishDate, bool requireApproval)
            : base(sourceDatabase, targetDatabase, mode, language, publishDate)
            RequireApproval = requireApproval;

        public bool RequireApproval { get; set; }

Now you are ready to launch publishing with workflow settings completely ignored. For example, this is how you can do it from the workflow action:

var database = Factory.GetDatabase(databaseName);

var options = new ExtendedPublishOptions(dataItem.Database, database, PublishMode.SingleItem, dataItem.Language, DateTime.Now, false)
         RootItem = dataItem,
         Deep = true;

new Publisher(options).PublishAsync();

3/11/2010: Correction » the last parameter of the ExtendedPublishOptions constructor should be “false”.
Instead of hardcoding it, I’d suggest having a parameter on the level of a workflow action which you can read and pass on.

private static bool RequireApproval(string parameters)
         return WebUtil.ParseUrlParameters(parameters)["RequireApproval"] == "true";
public void Process(WorkflowPipelineArgs args)
               var requireApproval = RequireApproval(innerItem["parameters"]);
               var options = new ExtendedPublishOptions(dataItem.Database, database, PublishMode.SingleItem, dataItem.Language, DateTime.Now, requireApproval)

That’s it!

Tested on Sitecore 6.1. Expected to work with 6.2.


Kamsar said...

Nice work, allowed me to eliminate a hack in some internal library code (previously it removed the item from workflow, published it quickly, then put it back where it was...sigh)

Kamsar said...

I like this technique but it has a downside: if you run a smart publish site to your staging database it will "unstage" any items that workflow staged into them with the current published version, ignoring the workflow state.

Alex Shyba said...

You are right. There is a solution for that though - take this logic to the publish pipelines. This is working for one of the customers but I did not have a chance to update the article.

boro2g said...

Which events in the publish pipelines would you need to tap into to solve the problem Kamsar mentions?

Do you have examples of this working?

Thanks in advance

Alex Shyba said...

boro2g, I just posted an alternative solution that should work better and does not require extendedpublishoptions.