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:
- A custom list is associated with the Approval workflow. One of the columns is of type "Person" which accepts an SPUser object. This user will be the approver of the instance of this workflow.
- A custom web part captures data from a user, and creates an item in a list.
- A Feature provisions the above list, and welds on the Approval workflow with custom Association data.
- Event receivers are installed for the custom list as well as the task list the workflow uses. These will kick off the workflow, and execute code when it is approved.
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.
- Line #28 creates the task list for the workflow, and stores a reference to it.
- Lines #31 and #32 are the meat and potatoes for programmatically creating a workflow.
- Lines #34 - #39 configure the workflow. Notice that some of the properties of the association can be set "normally" while others are set via (shitty) string manipulation of the association metadata. This is the first taste of how we will be setting a dynamic approver. The association metadata should be manipulated when you want to customize default settings per instance of a workflow; the Boolean properties are for the workflow's behavior as a whole.
- Lines #42 and #43 implement the wiring of the event receivers for the actual request list, as well as the workflow task list. Notice in Line #35 we are not auto-starting any workflow instances, because this does not give us the opportunity to customize our initialization data. Instead, we use an event receiver on the list to read in the list data and programmatically kick off a workflow. In Line #43 , I hook the same ItemAdded event on the workflow task list, so, pending the outcome, I can react to approvals or rejections.
Next, let's look at the code that kicks off the workflow: