It is always with a bit of hesitation that I recommend using Out-Of-The-Box SharePoint Workflows to my clients. It's not because they are exactly terrible or anything; it's just another application of the 80-20 rule. And since I take such good care of my clients, they seem to become fairly well acclimated to never hearing phrases like "No" or "That's out of scope" or "That's not in the budget" or "No thanks, I've already eaten."
So when the need arises for a quickie approval process on a document library or list, the OOTB Approval Workflow should bubble into your mind immediately. But as soon as a client squeaks about some minor customization here, or drops a "would it be possible" there, the bubble will quickly pop, sending you off to Visual Studio 2008 and the WF Workflow Designer, ultimately reinventing about ninety percent of the wheel.
Despite the fact that there are a lot of configuration options for the OOTB SharePoint workflows, modeling a business process is so specific to an organization's way of doing things that a generic "Approval" workflow can rarely be expected to get the job done. Except for the most remedial cases, the Approval workflow is not a panacea for content approval.
However, that doesn't mean that it's time to immediately build a workflow from scratch! By using the SharePoint workflow API and some nifty manipulations of the Association and Initialization data, we can leverage the OOTB Approval workflow and customize, or, more accurately, force it to do what we need.
One customization that always seems to come up is assigning a dynamic approver to an instance of an Approval workflow. On the association screen, you can specify a list of static approvers, but that's really it. If your workflow is set to kick off automatically (via an ItemAdded or ItemUpdated event), there's no opportunity to specify initialization data for that instance.
So what I'm going to show is how to programmatically create and start an instance of an Approval workflow with custom initialization data that is built from metadata on the list that the workflow is associated with. Let's start by an overview of the architecture of this scenario:
Taking the last item first, here's some sample code for a feature receiver's FeatureActivated event, scoped at the site level:
I know that's a long method and there's a lot going on, so let's look at some of the important lines in the above listing. The rest are examples of how I like to customize my sites via code executed in feature receivers, instead of mammoth XML site definition files.
Next, let's look at the code that kicks off the workflow:
Everything boils down to Line #27 that invokes the StartWorkflow method on the WorkflowManager class, which hangs off of an SPSite object. This method takes in three parameters:
It is this third parameter that inspired this post. Where the hell does the initialization data come from? If you Bing around, you'll see that the idea is to pass in XML (most desirably a serialized object) to define the metadata of the instance of the workflow. Now this probably isn't a big deal for a custom built workflow, but how do you anticipate what the initialization data will be for an OOTB workflow?
Turns out, it's the same XML as the association data! This can easily be inspected in debug mode by checking out the value of the "association" variable in the first listing above, after Line #33. Then, down to the second listing, all I do to set an approver is grab the SPUser object from the list item data in Line #22, and use its properties to fill in the XML of the initialization data string in Line #25.
Then simply pass your modified XML, along with the list item and the association object, to the StartWorkflow method, and you're off!
The last step here is to figure out how to get code to run when the workflow instance is approved. My approach was to use another event receiver, but this time on the workflow task list itself. Hooking the ItemUpdated event and checking the task item's "Outcome" column for a value that starts with "Approved" will get us there. Here's the code:
That's it! As you can see, you can push OOTB workflows pretty far beyond the customizations available on the association page during configuration. But don't go too nuts extending these workflows; depending on your requirements, it might just take less time to build it from scratch!