Programmatically modifying SharePoint workflows

Sunday, Feb 22, 2009 4 minute read Tags: sharepoint rant
Hey, thanks for the interest in this post, but just letting you know that it is over 3 years old, so the content in here may not be accurate.

First off, let me start by saying that I often really hate SharePoint. Well maybe I should be a bit more specific, I really hate Publsihing Portals in Microsoft Office SharePoint Server 2007.
Windows SharePoint Services 3.0 I think is a great product, and the MOSS extensions are really quite awesome (Excel Services, enterprise search, etc), but Publishing Portals are shit.

Ok, I better stop or I'm going to keep ranting and not do this post.

I'm building a MOSS publishing site at the moment, and the client wants a multiple stage workflow. Luckily I found a really great blog post on how to do that with the standard MOSS approver workflow (link), but there's a problem, this only modifies the workflow on the current Publishing Site, any time a new one is created the change has to be done again.

Now I really didn't want to have to have the client constantly doing this, I kept thinking there has to be a better way. So I did what any good SharePoint developer does, pulled out my copy of Reflector and started digging through the workflow code.

Then I got sad, the workflow is an InfoPath form it seemed, or at least I have no really easy way to edit it. It doesn't seem like there is a way in which I can do anything to the workflow, because the workflow doesn't really maintain the data I need, then I found the SPWorkflowAssociation class, this is what maintains the relationship between the workflow template selected (see SPWorkflowTemplate) and the SPList (in my case, the Pages list of the Publishing Portal).

So now I'm really boned, I don't seem to have anything within the API I can do to set the workflow approver accounts, my other option is to create a new Site Definition.
Site Definitions are scary, really bloody scary. I've read the theory on how to do them but never attempted it, and I'm not sure it's something I want to try and learn on-project!


So while staring at the API I had an epiphany, SPWorkflowAssociation has a property AssociationData, I did a dump with LINQPad to work out what it contained and wow, this is exactly what I wanted, it may be in XML format but I can deal with that. Any SharePoint developer is use to working with XML which has next to no documentation (CAML is painfully undocumented, and then there's Solutions, Features, Site Definitions, etc!) so I can deal with this.
Well there's an easy way to get my XML, just create is as-needed and then set it as a string back to a new instance.

And then the next problem came up, there's no Update method on SPWorkflowAssociation! Damnit! But you wouldn't not believe how happy I was to find SPList.UpdateWorkflowAssociation(SPWorkflowAssociation), oh yeah, WIN!

Then I end up with this bit of code:

using(SPSite site = new SPSite("http://localhost")) {
using(SPWeb web = site.OpenWeb()){
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);
SPWorkflowAssociation wfa = pubWeb.PagesList.GetWorkflowAssociationByName("Parallel Approval");
SPWorkflowAssociation wfa = pubWeb.PagesList.WorkflowAssociation["Parallel Approval"];
wfa.AssociationData = @"..."; //ommited

I've not included the XML as it's a bit large, but it's not exactly required.

The next question is how do you deploy this? You need a way that it can be done for each new site created, but as far as I'm aware there isn't any way I can tie into the site creation.
So to get around this I've come up with an idea I was quite happy with, a SharePoint Feature, scoped at a Web level.

Then all that is needed to do is when a new site is created the feature can be manually activated. This makes it nice and easy to have a deployable WorkflowAssociation. You could (kind of) use the above code to deploy a new Workflow Association, if required.

*Edit - As Keith has pointed out I had a mistake, there is no name indexer on SPWorkflowAssociationCollection. I was coding from memory and thought I had it right, guess not. I've updated the example to what it should be. I should stop blogging from memory if it wasn't the day I wrote the code :P