Programmatically approve a SharePoint workflow task

I know they are several samples available out there, but here is a merge the best parts I found.

This example will show how to notify a sharepoint workflow to move to next step.

Context
I'm creating a custom workflow. I need to notify the wf from outside the process (list event, console app,etc...).

The Workflow
The basic is to create a "fake" task and monitor the OnTaskChange event, then programmatically approve the task from outside the process.


The wf example is pretty simple: create a task, wait for changes then log a message in the workflow history.

Bind the CorrelationToken of the CreateTask1 to a new token on that will be exclusive for this task. Apply the same token to the OnTaskChanged1.

The while loop could be omitted in this example, I left it to suggest that you can add a validation at this point using the while condition.
private void createTask1_MethodInvoking(object sender, EventArgs e)
{
  var ct = sender as CreateTask;
  ct.TaskId = Guid.NewGuid();
  var spTaskProperties = new Microsoft.SharePoint.Workflow.SPWorkflowTaskProperties();
  spTaskProperties.Title = String.Format("{0} Step1", this.workflowProperties.Item.Name);
  ct.TaskProperties = spTaskProperties;
}

No need to bind the OnTaskChanged Invoked event.
The CreateTask1_MethodInvoking provides the minimal infos to create the task, note that the AssignedTo property is left blank.

NOTE: In the feature that deployed the workflow I didn't specify an infopath form.
Therefore if you manually go to the workflow task list and click on the task you will get a message telling you that no xsn form is associated, nothing to worry about we'll do it through code :)

Notifying the workflow
In this example I chose to fire next step of the workflow from a console application.
static void Main(string[] args)
{
  using (var site = new SPSite("http://[site_url]"))
  {
    using (var web = site.OpenWeb())
    {
      // retreive the list where the workflow is running
      var list = web.Lists["myList"]; 

      // find the item with the task to complete (for the example my item's ID is 1)
      var item = list.Items.Cast<SPListItem>().First(c => c.ID == 1);
      
      // find the latest active workflow
      var wfs = site.WorkflowManager.GetItemActiveWorkflows(item).Cast<SPWorkflow>();
      var wfID = wfs.OrderByDescending(c => c.Created).First();

      // fetch workflow tasks
      var wfTasks = item.Tasks;
      
      // find the correct task
      var task = (SPListItem)wfTasks.Cast<SPWorkflowTask>().First(
        c => c.WorkflowId.Equals(wfID.InstanceId)
        && c.Title.Contains("Step1"));

      // build a hashtable with the values to be changed to mark the task as complete
      var ht = new Hashtable();
      ht[SPBuiltInFieldId.Completed] = true;
      ht[SPBuiltInFieldId.PercentComplete] = 1;
      string taskStatus = SPResource.GetString(new CultureInfo((int)task.Web.Language, false), "WorkflowTaskStatusComplete", new object[0]);
      ht[SPBuiltInFieldId.TaskStatus] = taskStatus;

      // alter the task using a trick to prevent task lock issue
      AlterTask(task, ht, true, 5, 100);
    }
  }
}


public static bool AlterTask(SPListItem task, Hashtable htData, bool fSynchronous, int attempts, int millisecondsTimeout)
{
  // check this link for more details: http://geek.hubkey.com/2007/09/locked-workflow.html

  if ((int)task[SPBuiltInFieldId.WorkflowVersion] != 1)
  {
    SPList parentList = task.ParentList.ParentWeb.Lists[new Guid(task[SPBuiltInFieldId.WorkflowListId].ToString())];
    SPListItem parentItem = parentList.Items.GetItemById((int)task[SPBuiltInFieldId.WorkflowItemId]);
    for (int i = 0; i < attempts; i++)
    {
      SPWorkflow workflow = parentItem.Workflows[new Guid(task[SPBuiltInFieldId.WorkflowInstanceID].ToString())];
      if (!workflow.IsLocked)
      {
        task[SPBuiltInFieldId.WorkflowVersion] = 1;
        task.SystemUpdate();
        break;
      }
      if (i != attempts - 1)
        Thread.Sleep(millisecondsTimeout);
    }
  }
  return SPWorkflowTask.AlterTask(task, htData, fSynchronous);
}



And voila!

Comments

Anonymous said…
Awesome, works great.
Peter Morris said…
Thanks. It helped. I have just signed up for a free SharePoint site with http://www.cloudappsportal.com
Leslie Lim said…
Wow. Awesome article. Please do more articles like this in the future. Very informational and knowledgeable. I will expect more from you in the future. For now i will just bookmark your page and surely I'm gonna come back later to read more. Thank you to the writer!


Rica
www.imarksweb.org
Silvia Jacinto said…
I love your blog. Keep it up.Visit my site too.

n8fan.net

www.n8fan.net