codeflood logo

No Workflow for Web

I was reading a post by Alex Shyba recently which showed how you can get workflow history using the Sitecore API. This post reminded me of a post I was going to write a while ago on the subject of the workflow APIs. At the bottom of the above post, Alex points out that you could use the code in a workflow email action to get the history and send an email to the user who submitted the item into the current workflow state.

This code runs fine when it's being executed as part of a workflow, but it will fail if you try to run it from the website. Why might I want to run this code (or similar) on the website? Allow me to elaborate.

I love the simplicity and power of the Sitecore workflow engine. And I don't just use it for content approval. Let's say we have a "contact us" form on the website. Usually the way these forms works is they collect the information from the user then email it to a predefined email address. You might even pull the email address from the content tree to allow admins to update the recipient without having to recompile the app. But email is not a guaranteed delivery mechanism. So in this case, instead of relying on email, I prefer to store the "contact us" data which has been submitted in the content tree. What's more, after creating the item to store the data, I put the item into workflow (well, it goes into workflow automatically).

There are several benefits to using workflow for this kind of non-content data. Firstly, even if not defined, "contact us" data does follow a workflow. At it's simplest, it starts off in the "received" state and would transition to the "actioned" state after someone had read it. Sitecore workflow is a great way to track and make sure all "contact us" data has been actioned. You could also extend this simple workflow to include sending the data off to more appropriate parties. The initial reviewer might determine it's a product related query and execute the "send to marketing" workflow command.

With the data in workflow it's very easy for reviewers to see all the "contact us" data that requires their attention in their workbox. The marketing people don't even see the data until the initial reviewer has looked over the item and determined that it is in fact a query that marketing needs to deal with.

It's also very easy to extend the workflow to include sending emails. You might have an action on the initial workflow state that sends an email to the initial reviewer alerting them that new contact data has been received. You could also incorporate emails which are sent to the submitter (website user) telling them what's going on with their query.

"Thanks for your email. We've got it and we're looking into it".

"Thanks for your email. Someone from our marketing team will contact you shortly."

So back to making the code work on the website. Have you ever attempted to run code that calls the workflow API from the website? It will fail. Why? Because the Workflow provider is null. Why? Well, have a look at your web.config and the database and site definitions. The master database contains nodes to create the workflow provider. The web database does not have the required configuration to run workflow. The "website" site also does not have the enableWorkflow attribute set to true as the "shell" website does. You not only have to enable workflow on the database, you also need it enabled on the current site context.

If you're going to be creating items from the live website (such as in the example above), make sure you're creating them in the master database. If you don't and you create them in the current context database, you'll end up losing those items on the next publish. This will take care of placing the item into the correct workflow and workflow state upon creation.

And because we're now operating the in master database, the workflow provider is no longer null and we can make other workflow API calls. Although, if you're still on the "website" site, many of these calls will fail. You can swap over to the "shell" site by calling Context.SetActiveSite("shell"). Incidently the code Alex posted will all work without changing to the "shell" website, but my code at the bottom of this post will.

Let's say the "contact us" item we create as a result of submitting the "contact us" form is more complex than an average form, and there are lots of fields to input on the item. We've also added a workflow action to the initial state to send an email to the initial reviewer and we include some item data such as title and submitters email in the email to the reviewer. If this action was placed on the initial workflow state, then when the action reads the item data it will likely be empty. This is because the action is executed as soon as the item lands in that state. So as soon as the item is created, and before you have a chance to enter the data into the item, the action is executed and the email containing empty data will be sent.

To work around this, you just need to introduce a state before the initial state above. When all the data is entered, we then execute the workflow command to move the item along to the next state, where the action will now execute with the item's field data available.

The following code will create a new item, populate the fields, then find the command with name "submit" on the initial workflow state and execute it to move the item to the next workflow state.

string sitename = Context.Site.Name;
Context.SetActiveSite("shell");
Database db = Configuration.Factory.GetDatabase("master");
Item item = db.GetItem("/sitecore/content/home/contact").Add("my item",
  db.Templates["user defined/contact"]);
using(new EditContext(item))
{
  // Fill in item fields
}
IWorkflow workflow = db.WorkflowProvider.GetWorkflow(item);
WorkflowCommand[] commands = workflow.GetCommands(item);
WorkflowCommand command = null;
for (int i = 0; i < commands.Length; i++)
  if (string.Compare(commands[i].DisplayName, "submit", true) == 0)
    command = commands[i];

if (command != null)
{
  WorkflowResult result = workflow.Execute(commands[0].CommandID, item,
    "Item Created", false);
  if (!result.Succeeded)
  {
    // Command failed
  }
}
SC.Context.SetActiveSite(sitename);

Note I preserve the current site name at the top before I set "shell" as the active site and I revert back to the initial context site when I'm done with the workflow API.

Comments

Excellent post, it goes much deeper than "what to I do, there's no workflow in web".
I wonder how hard it is to add custom set of buttons to Outlook email, so that you can execute workflow actions with these buttons (probably using web service behind the scenes)

That was on my mind of a while now. With VSTO and WCF it should be pretty easy to do. Now it is a question of finding time to do that ;-)

One comment on the actual post, maybe too obvious but worth mentioning. The solution seems to be applicable to the single server setups since the context change calls and GetDatabase("master") require access to the "shell" site and master database which may not be available in case of decoupled server with a separate lightweight content delivery instance.

Alistair Deneys

I smell another shared source module! :) Thanks for pointing out the multiple server setup Alex. I usually get around that issue with WCF which calls back to the "primary" server. You would run any workflow code inside your service being hosted from the "primary" server as you wouldn't normally have content in the master database of the lightweight content server.

Not that I'm impressed a lot, but this is a lot more than I expected when I found a link on Delicious telling that the info here is awesome. Thanks.

Lots of Fantastic information in your posting, I favorited your blog so I can visit again in the future, Thanks, Ivan Dormer

Leave a comment

All fields are required.