A few weeks ago i have been struggling with programmaticly adding a standard approval workflow to a custom list.
Since this is an out of the box functionality I expected this to go pretty smoothly… NOT… :(
It seemed to be some xml manipulations which is pretty different from SharePoint 2007.

The basic steps for adding the standard approval WF to a library is pretty easy:

string workflowAssocName = "Your association name";
SPWorkflowTemplate workflowTemplate = oWeb.WorkflowTemplates.GetTemplateByName("Approval - SharePoint 2010", System.Globalization.CultureInfo.CurrentCulture);
SPList myList = oWeb.Lists["LibraryName"];

The workflowAssocName is nothing but the “display name” of the workflow association on the list. This can be anything.

// Try to get workflow history and task list
historyList = TryGetList(oWeb, Constants.WORKFLOW_VERGADERWERKRUIMTE_DOCUMENTEN_LISTS_HISTORY, "", true, SPListTemplateType.WorkflowHistory);
taskList = TryGetList(oWeb, Constants.WORKFLOW_VERGADERWERKRUIMTE_DOCUMENTEN_LISTS_TASKS, "", true, SPListTemplateType.Tasks);

TryGetList is a function which will get the list if it allready exists at the site. If not, the list will be created.

private SPList TryGetList(SPWeb oWeb, string listTitle, string listDescription, Boolean hidden, SPListTemplateType Type)
{
      SPList oList;
      try
      {
            oList = oWeb.Lists[listTitle];
             return oList;
       }
       catch (ArgumentException exc)
       {
        // Create list
        Guid listGuid = oWeb.Lists.Add(listTitle, listDescription, Type);
        oList = oWeb.Lists[listGuid];
        oList.Hidden = hidden;
        oList.Update();
        return oList;
      }
}

To create the workflow association add this code.

// Create workflow association
SPWorkflowAssociation workflowAssociation = SPWorkflowAssociation.CreateListAssociation(
workflowTemplate, workflowAssocName, taskList, historyList);

// Set workflow parameters 
workflowAssociation.AllowManual = false;
workflowAssociation.AutoStartCreate = true;
workflowAssociation.AutoStartChange = true;
workflowAssociation.ContentTypePushDown = false;

// The AssociationData property represents for the settings that user see in 2nd screen when creating workflow in SharePoint UI
var associationDataXml = XElement.Parse(workflowAssociation.AssociationData);

// Update modifed xml segment back to WorkflowAssociation object
workflowAssociation.AssociationData = SetAssociationData(oWeb.ParentWeb, associationDataXml);

// Add workflow association to my list
myList.WorkflowAssociations.Add(workflowAssociation);

// Enable workflow
workflowAssociation.Enabled = true;
myList.Update();

The tricky part comes in at this line

workflowAssociation.AssociationData = SetAssociationData(oWeb.ParentWeb, associationDataXml);

AssociationData is an xml format of the data which you normally enter in the association form. (When adding a standard approval workflow using the UI).

 private String SetAssociationData(SPWeb oWeb, XElement associationDataXml)
 {
    String sDate = String.Empty;
    XNamespace dNamespace = @"http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields";
    XNamespace pcNamespace = @"http://schemas.microsoft.com/office/infopath/2007/PartnerControls";
    XNamespace dfsNamespace = @"http://schemas.microsoft.com/office/infopath/2003/dataFormSolution";
    XNamespace MyNamespace = @"http://schemas.microsoft.com/office/infopath/2003/myXSD";
    XNamespace XsiNamespace = @"http://www.w3.org/2001/XMLSchema-instance";

    XElement propertiesWrapper = associationDataXml.Element(dfsNamespace + "dataFields").Element(dNamespace + "SharePointListItem_RW");
    propertiesWrapper.SetElementValue(dNamespace + Constants.WORKFLOW_SETTINGS_CANCELONREJECTION, true);
    propertiesWrapper.SetElementValue(dNamespace + Constants.WORKFLOW_SETTINGS_CANCELONCHANGE, true);
    propertiesWrapper.SetElementValue(dNamespace + Constants.WORKFLOW_SETTINGS_APPROVEWHENCOMPLETE, true);
    propertiesWrapper.SetElementValue(dNamespace + Constants.WORKFLOW_SETTINGS_ENABLECONTENTAPPROVAL, true);
    propertiesWrapper.SetElementValue(dNamespace + Constants.WORKFLOW_SETTINGS_EXPANDGROUPS, false);

    // Reviewers is Approvers in UI
    XElement xAssignement = associationDataXml.Element(dfsNamespace + "dataFields").Element(dNamespace + "SharePointListItem_RW").Element(dNamespace + "Approvers").Element(dNamespace + "Assignment");

    string ApprovalGroup = "Approver Group Name";
    xAssignement.Element(dNamespace + "Assignee").Add(
        new XElement(pcNamespace + "Person",
              new XElement(pcNamespace + "DisplayName", ApprovalGroup),
              new XElement(pcNamespace + "AccountId", ApprovalGroup),
              new XElement(pcNamespace + "AccountType", "SharePointGroup")
        )
    );
    return associationDataXml.ToString();
}

Tip: Before doing this, check the associationdata property of a workflow association added through the UI. (You can open this property using SharePoint Manager 2010).

At first i define some namespaces since they are used in this xml, this means i can not access nodes without including the namespace.

The next step is changing the necessary properties. (Like cancel on reject etc) I have changed 5 values, tho you can change all of them. For a list of available properties check the xml file as explained in the tip.

The last step is to make sure the correct SharePoint User or Group is set as approver for this WorkFlow. It took me a while to get this.
But it seems that there is an empty Assignee tag which is the container for our approvers.
This tag expects a Person child node which also expects 3 child nodes: DisplayName, AccountId and AccountType.

When running this code on an existing SharePoint document library, it should successfully attach a workflow and create a task when a new item is added.

Tagged with:
 
  • Tom

    Hi – thanks for a fantastic blog post – this really helped me out. Can I just ask though – although I have the code working the cancel on change, cancel on rejection and approve when complete aren’t working for me. What constants are you using? I was using CancelonRejection, CancelonChange and EnableContentApproval which I was able to get from looking at the xml on the workflow properties but these don’t appear to work. When I set these properties manually though the UI the workflow stops when someone cancels and the rejected status is added to the ListItem, however, starting the workflow through code doens’t cancel or update the listitem which makes me think I have the properties set incorrectly

  • http://blog.voltje.be Frederik Prijck

    Hello Tom,
    The constanst I was using are:
    public const string WORKFLOW_SETTINGS_CANCELONREJECTION = “CancelonRejection”;
    public const string WORKFLOW_SETTINGS_CANCELONCHANGE = “CancelonChange”;
    public const string WORKFLOW_SETTINGS_APPROVEWHENCOMPLETE = “ApproveWhenComplete”;
    public const string WORKFLOW_SETTINGS_ENABLECONTENTAPPROVAL = “EnableContentApproval”;
    public const string WORKFLOW_SETTINGS_EXPANDGROUPS = “ExpandGroups”;

    They worked for me so I hope this is of any help to u!

    Kind regards,

  • Vandeput

    Hi Frederik,

    Nice blogpost, great job :-)

    Now I want to chang the approvers for each item in code.
    What I do is changing the associationDataXml when a new item is add and then start the workflow programmatically for that item.
    But the workflow is canceled automatically with the following message: Coercion Failed: Input cannot be null for this coercion.

    Any idea?

    Grtz

    Vandeput

  • http://blog.voltje.be Frederik Prijck

    Hey Christophe.
    Nice to see a Belgian guy arround.
    If I understand what you want to say, you are changing the associatedXml everytime a new item is added?
    I think this is not a good idea, since the xml is stored on the template base and you are changing the template everytime…
    I can be wrong, but I have never done anything like this.

    If you want, feel free to show me your code and xml but i can not promise to be of any help.

    Grtz

  • Christophe

    Frederik,

    It was my mistake.
    First a had SPWorkflowAssociation.CreateWebAssociation, but it has to be SPWorkflowAssociation.CreateListAssociation.

    Changing the associateXml for each item isn’t a problem, because it’s just an instance, so the orignal xml isn’t modified :-)

    I really need this because the approvers are different for each item.
    Or maybe you have a better approch for this?

    Grtz

    Christophe

  • http://blog.voltje.be Frederik Prijck

    If it works i wouldn’t worry about it. I guess im just mistaking!

    Good luck