Programmatically applying and configuring an out-of-the-box workflow to a SharePoint list isn’t the most obvious thing to do but fortunately it’s not exactly hard either. I had to dig around a bit to find the pieces I need to do this and thought I’d share…
List Settings - Versioning
In my case I’m applying an approval workflow to a Pages library created by the Publishing feature; before I wrangle the workflow I need to configure the versioning settings on my Pages list. Through the UI, I can do this by configuring these settings as follows:Essentially, content approval is required, users need to be able to create major and minor (draft) versions, all editors should be able to see all drafts, and checkout is required to edit documents.
To do this setup programmatically, I set the following properties on my Pages list:
SPList pages = web.Lists ["Pages"];
// Require content approval
pages.EnableModeration = true;
// Create major and minor versions
pages.EnableVersioning = true;
pages.EnableMinorVersions = true;
// Who should see draft items
pages.DraftVersionVisibility = DraftVisibilityType.Author;
// Require documents to be checked out before they can be edited
pages.ForceCheckout = true;
Remove Existing Workflows
In some cases, you may also want to cleanup a list’s existing workflows (i.e. remove them) and this is easily accomplished using the SPList.RemoveWorkflowAssociation() method. Note several examples on the interwebs suggest doing this within a foreach loop but doing so will invalidate the enumerator after the first item is removed; use a for loop or a while loop instead:
// Remove existing workflows
for (int associationIndex = 0; associationIndex < pages.WorkflowAssociations.Count; associationIndex++)
SPWorkflowAssociation workflowAssociation = pages.WorkflowAssociations [associationIndex];
If you need to remove individual workflows, inspect the SPWorkflowAssociation.Name property.
With that out of the way, let’s move on to the workflow configuration proper. In short, it’s all about the collection of SPWorkflowAssociation objects exposed through the WorkflowAssociations property of the SPList class. The AssociationData property exposed by the SPWorkflowAssociation class is also crucial.
Task and History List Management
When you setup a workflow through the UI, you’ll be prompted to specify names for the task and history lists (the latter of which will be hidden); when you’re doing this programmatically, you have to manage this part yourself. It would have been nice if the API did this automatically but it doesn’t. The CreateListAssociation method we’ll examine in a moment also requires a reference to each list so you need to a) determine if each list exists or create it and b) retrieve a reference to each list. To wrap all of this logic up together, here’s my helper method (based on this by Marco):
private void EnsureWorkflowLists (SPWeb web, out SPList taskList, out SPList historyList)
const string WORKFLOW_TASK_LIST_NAME = "Workflow Tasks";
const string WORKFLOW_HISTORY_LIST_NAME = "Workflow History";
// Task list
taskList = web.Lists [WORKFLOW_TASK_LIST_NAME];
Console.WriteLine ("Tasks list found.");
// List does not exist so create it
Guid taskListId = web.Lists.Add (WORKFLOW_TASK_LIST_NAME, string.Empty, SPListTemplateType.Tasks);
taskList = web.Lists [taskListId];
Console.WriteLine ("Created task list for web.");
// History List
historyList = web.Lists [WORKFLOW_HISTORY_LIST_NAME];
Console.WriteLine ("History list found.");
Guid historyListId = web.Lists.Add (WORKFLOW_HISTORY_LIST_NAME, string.Empty, SPListTemplateType.WorkflowHistory);
historyList = web.Lists [historyListId];
historyList.Hidden = true;
Console.WriteLine ("Created history list for web.");
Call EnsureWorkflowLists method like so:
EnsureWorkflowLists (web, out taskList, out historyList);
Before you can finally create the new workflow association, you’ll also need an SPWorkflowTemplate. You can do this by calling the GetTemplateByName method hanging indirectly off the SPWeb object:
SPWorkflowTemplate approvalTemplate = web.WorkflowTemplates.GetTemplateByName ("Approval", System.Globalization.CultureInfo.CurrentCulture);
If you have any issues here, ensure the Routing Workflows feature (or the relevant feature for whatever OOB workflow you’re dealing with) is enabled at the site collection level (/_layouts/ManageFeatures.aspx?Scope=Site).
Create the Workflow Association
With the leg work behind us, actually applying a new workflow association to the list and configuring it is quite easy, with one exception I’ll cover in a moment. To begin, create a new workflow association using the SPWorkflowAssociation.CreateListAssociation method and supply the workflow template and list references created previously:
SPWorkflowAssociation approvalWorkFlowAssociation = SPWorkflowAssociation.CreateListAssociation (approvalTemplate, "Page Approval", taskList, historyList);
Note the name parameter “Page Approval” is arbitrary but will appear in the UI and notification emails; I therefore aim to make it user friendly and somewhat specific.
As mentioned, there’s one last hurdle and that’s providing the XML string to the SPWorkflowAssociation.AssociationData property. As far as I’m aware, this is the only way to specify the nominated approvers group and a few other properties such as the ability to delegate tasks and whether a change to the list item should interrupt the current workflow. I believe the easiest way to create this string is to copy an example (below) or configure the workflow as desired in the UI and use SharePoint Manager to extract the XML from the workflow association (highlighted in the image below).
<my:myFields xml:lang="en-us" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD">
Drop the XML into the SPWorkflowAssociation object you just created using the AssociationData property and you’re all set to add the association to your list using the SPList.AddWorkflowAssociation method:pages.AddWorkflowAssociation (approvalWorkFlowAssociation);Before updating the list list, you may also want to configure your new workflow as the default content approval workflow:pages.DefaultContentApprovalWorkflowId = approvalWorkFlowAssociation.Id;Follow that with pages.Update () and you’re done!