Sunday, 3 February 2008

InvokeWorkflow - child workflows not supported in SharePoint?

Our client through an interesting curve ball at us midway through my workflow project. We were probably 70% code complete, when it was realised the design had no provision for a certain scenario. The workflow is triggered by a user completing an InfoPath form - this has many input controls to collect data, and several of them are dropdowns which dictate the particular path through the workflow which occurs. One such dropdown looks something like:

Type:

  • type 1
  • type 2
  • both

The value selected is evaluated by IfElse conditions in the workflow - if "type 1" is chosen, we go down a particular branch, if "type 2", a different branch and so on. Now for this particular dropdown, we realised we hadn't specified what should happen for the "both" condition (that's agile for you!). The new requirement which arose was that notifications and tasks should go to to two teams (i.e. both of them), and after further discussion we decided it would be appropriate to kick off two separate workflows, one for each team.

Most workflow frameworks can deal with this by using 'child' workflows, and Workflow Foundation is no different. After a couple of hours refactoring the implementation to use this approach, my proof-of-concept looked like:

ParentWorkflow

So whilst the 'InvokeWorkflow' activities are closed (i.e. the child workflows aren't expanded), you can hopefully see that if we go down the branch on the left, we launch one child workflow, but in the branch on the right (for "both"), we launch two child workflows in parallel.

The problem

I tried a couple of things, the image above shows using a sequential parent workflow (though the child workflow is state-machine), but couldn't get this to work. Using the debugger I could see that the child workflows were being initialized - I could step through InitializeComponent(), but breakpoints in 'OnWorkflowActivated' were never hit - the event wasn't raised. The SharePoint log showed:

System.Workflow.Activities.EventDeliveryFailedException: Event "OnWorkflowActivated" on interface type "Microsoft.SharePoint.Workflow.ISharePointService" for instance id "67c4929a-9280-4b8b-9a4c-a1b85b04267c" cannot be delivered. ---> System.InvalidOperationException: Event Queue operation failed with MessageQueueErrorCode QueueNotFound for queue 'Message Properties Interface Type:Microsoft.SharePoint.Workflow.ISharePointService Method Name:OnWorkflowActivated

So I became suspicious of whether it's possible to use InvokeWorkflow in a SharePoint workflow. Microsoft haven't yet got back to me on this, and the most detailed SharePoint workflow book I'm aware of (Workflow in the 2007 Microsoft Office System by David Mann) does not discuss InvokeWorkflow.

Time for some lateral thinking.

The solution

We know that we will definitely have two workflows if two completed InfoPath forms are added to the document library, so I asked the client what they thought of getting the system to split a "both" request into two separate items - this would mean having two separate InfoPath forms in the list. The answer was "actually that's probably better for the users come to think of it", so the solution I came up with was:

  • a list event receiver which executes before the workflow - this does the following:
    - opens the binary contents of the InfoPath form as a string, and modifies the dropdown value from "both" to "type 1"
    - creates a duplicate of the original file in the same library, but modifies the dropdown value here from "both" to "type 2"
  • running some ad-hoc code to reorder the event receivers on the list - we need to split the item before workflow starts (so we get two workflows) but SharePoint adds the SPWorkflowAutostartEventReceiver at sequence of 1 so it executes first. This code switches the event receiver sequence so our pre-processing happens first.

On the last point, I considered that the SharePoint team may have set workflow to execute first for a reason, but then again it's just another event receiver and SharePoint does not prevent this reordering (in the same way it does with some other changes, such as deleting the welcome page from a web for example). In any case, we've tested pretty extensively and the solution hasn't missed a beat.

In case it's useful, the following code can be used to duplicate and modify a file - this could be used in a list receiver (as in my case) or in ad-hoc code. I used simple string manipulation to perform the modification since InfoPath forms are just XML, and also because using XML-handling methods caused issues with the 'proprietary' InfoPath declarations. Simply replace the string replacement line with your own processing.

   1: private string getFileContentsAsString(SPFile file)
   2: {
   3:     byte[] aFileBytes = file.OpenBinary();
   4:     string sContents = Encoding.UTF8.GetString(aFileBytes);
   5:  
   6:     return sContents;
   7: }
   8:  
   9: private void duplicateFile(SPListItem originalListItem, string sNewItemUrl, 
  10:     bool bDeleteOriginal)
  11: {
  12:     SPFile originalFile = originalListItem.File;
  13:     string sOriginalFileContents = getFileContentsAsString(originalFile);
  14:  
  15:     // TODO: replace this line with your own processing..
  16:     string sNewFileContents = sOriginalFileContents.Replace("oldValue", "newValue");
  17:     
  18:     byte[] aNewFileBytes = Encoding.UTF8.GetBytes(sNewFileContents);
  19:  
  20:     // we effectively need to add a new file for this operation, and optionally delete 
  21:     // the original..
  22:     SPFile newFile = originalListItem.ParentList.RootFolder.Files.Add(sNewItemUrl, aNewFileBytes,
  23:                                                                       originalFile.Properties);
  24:     newFile.Update();
  25:     
  26:     if (bDeleteOriginal)
  27:     {
  28:         originalListItem.Delete();
  29:     }
  30: }



P.S. If anyone is using InvokeWorkflow in a SharePoint workflow, leave a comment :-)

7 comments:

Andy Burns said...

Hi Chris,

Yeah, I'd a quick look at using subworkflows about, oh, about a year back now. I came to exactly the same conclusion - they don't seem to work.

Given that, and some of the problems we've had with Delay activities never restarting, there does seem to be a fairly compelling case for keeping workflows small, and starting them based on events in SharePoint. Or don't use the workflow at all, and drive the process through views and status fields. Of course, those both then lead you straight into the problems with reporting on SharePoint workflow, but that's another story.

Perhaps what we need is a user group session about 'Avoiding SharePoint Workflow'?

Oh, and K2 Workflow does subworkflows just fine - just sadly it comes with a training curve and price tag :(

Situation Normal - version 3 will get it right.

Jason Apergis said...

Yes - K2 will absolutely do what you need. It has worked in thier K2 2003 and Blackpearl products. For instance, I have been able to create more agile processes that can handle less stable business process by creating master processes with tons of child processes. So if a process instance were to kick off on a specific version, I can still deploy out new versions of child processes without having to stop the master process.

The price tag can be justified onces you have gone down the WF path. I know several projects that have tried this with really smart folks, it can be a painful process. There is a whole framework that comes with K2 and the value proposition really changes when you look at everything you get. MS provides the foundation like WF for frameworks like K2 to be built upon.

Here is something I wrote recently - http://k2distillery.blogspot.com/2008/02/blackpearl-moss-governance-case-study.html

Chris - Great blog - saved me several times...

ConvergePoint said...

Workflow still has a long way to go in SharePoint

Anonymous said...

Instead of using InvokeWorkflow Activity, I use Code Activity which I call WorkflowManager.StartWorkflow()

Works fine with me.

Chris O'Brien said...

Good tip. But I guess that would execute the child workflow asynchronously (aka "fire and forget") rather than waiting for the child workflow to complete before proceeding, being able to access a return value etc.

A useful point though, thanks.

Chris.

Anonymous said...

If you want your main Workflow to wait, use the While Activity with OnWorkflowItemChanged Event. Then the main workflow could wait for a return code set by the child workflow.

Not pretty but makes the job...

Tamás said...

As I know WorkflowManager.StartWorkflow() waits for the child workflow.