Wednesday 3 April 2013

Delete Workflow Item & Tasks

When you create custom task forms using Infopath and you delete your workflow item that the tasks are created for you get this lovely result when you try and open any of those tasks.
Why you may ask? I don't really know, but if I where to guess it would be that since it's a custom form it's stored with the workflow item instance and once it's deleted it no longer exists to be displayed, or it could be referenced by some sort of look up but since the intermediary is no longer available the link is broken; regardless we need a more elegant way of resolving this than "Sorry brah!"

How did I handle this? Well simple, I just deleted all the tasks ever associated with that item. Now we could argue till we're all blue in the face if that's a good way, good enough way, or a cop-out. Regardless it's what I went with, don't see a reason why you'd retain tasks about an item that no longer exists and if you're really worried about auditing you should utilize the history list.

Ok, so for real how'd I do this? well if you inspect the properties on the onWorkflowItemDeleted action you'll notice that it has an Invoked property. This is a pointer/reference to a function that fires after the activity is completed. Type Something in there and hit enter, I went with PendingItemDeleted_Invoked.


When you do this it creates an empty function, where I go ahead and call my delete.

private void PendingItemDeleted_Invoked(object sender, ExternalDataEventArgs e)
{
    DeleteTasks();
}

Seems pretty straight forward? cause it should, there's been no magic yet; it's about to get a little mind blowing, but you'll handle it. next we obviously have to take a coffee break, but right after that well make our DeleteTasks function, at least the first part of it.

private void DeleteTasks()
{
    Guid SiteID = onWorkflowActivated_New.WorkflowProperties.Web.Site.ID;
    Guid WebID = onWorkflowActivated_New.WorkflowProperties.Web.ID;
    Guid wfInstanceID = onWorkflowActivated_New.WorkflowProperties.Workflow.InstanceId;
            
    using (SPSite iSite = new SPSite(SiteID))
    {
        using (SPWeb iWeb = iSite.OpenWeb(WebID))
        {
            SPList taskList = iWeb.Lists["Approval Task List"];
            DeleteTasks(wfInstanceID, taskList);
        }
    }
}

Bam, the only tricky part is is grabbing the site and web id's; basically workflows run parallel to SharePoint, so you really can't use the context cause your not inside of it instead you just grab them off the onWorkFlowActivated action, vague enough for you?

right  so now for the grand finally deleting all of the tasks, using you guessed it the guid identifying the workflow running on our item that we just deleted also known as "Workflow Instance ID" or by it's internal name which you have to use for your caml "WorkflowInstanceID".

So without any further delay our delete function:

private void DeleteTasks(Guid wfInstaceID, SPList taskList)
{
    SPQuery query = new SPQuery();
    query.Query = string.Format(@"<Where><Eq>
                                    <FieldRef Name='WorkflowInstanceID' />
                                    <Value Type='Guid'>{0}</Value>
                                    </Eq></Where>", wfInstaceID);
 
    SPListItemCollection items = taskList.GetItems(query);
 
    for (int i = items.Count - 1; i > -1; i--)
    {
        items.Delete(i);
    }
}

The magic is actually not all that impressive, you have to remember to delete your list backwards from the last item to the first other wise you'll get a "Collection was modified; enumeration operation may not execute." error.

there are a couple other ways to skin this cat, this is just the one I went with.