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.



Tuesday, 2 April 2013

Output Task Fields

When you are developing workflows there`s many times when you need to interpret fields stored in your task list item or workflow properties contained in a hash table. Now you could very well step through your code but I find that way too much of a nuisance so I just created a class that takes in a file path, creates a log file and dumps the contents of these collections. it`s simple to implements but hey this is how I did it

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
namespace SharePoint.Magic.Workflow.Helper
{
public class propertyWriter
{
    private string _path = null;
    /// <summary>
    /// constructor to include file path where generated log files will be stored
    /// </summary>
    /// <param name="logPath">path to where log files will be stored ie "c:/logs/</param>
    public propertyWriter(string path)
    {
        if (Directory.Exists(path))
        {
            _path = string.Concat(path, string.Format("log{0}.txt"Directory.GetFiles(path, "log*.txt").Length));
        }
        else
        {
            throw new Exception(string.Format("Path does not exist, please ensure that {0} is a valid path", path));
        }
 
    }
 
    /// <summary>
    /// Print out list of keys with values from hash table
    /// </summary>
    /// <param name="properties">Hashtable to print</param>
    /// <param name="sectionTitle">section title you want in file</param>
    /// <param name="keys">array of keys that are of interest, ommiting will result in all entries being listed</param>
    public void writeProperties(Hashtable properties, string sectionTitle, params string[] keys)
    {
        using (System.IO.StreamWriter file = new System.IO.StreamWriter(_path, true))
        {
            file.WriteLine(sectionTitle);
            int i = 0;
 
            foreach (DictionaryEntry de in properties)
            {
                if (keys.Contains<string>(de.Key.ToString()) || keys.Length == 0)
                    file.WriteLine(string.Format("{3}\t {0}\t({1}) \t {2}", de.Key, de.GetType(), de.Value, i));
                i++;
            }
            file.WriteLine("");
        }
    }
 
    /// <summary>
    /// Print out all spfields in spfield collection of splistitem with keys and associated values
    /// </summary>
    /// <param name="li">sharepoint list item of interest</param>
    /// <param name="sectionTitle">section title you want in file</param>
    /// <param name="keys">array of keys that are of interest, ommmiting will result in all entries being listed</param>
    public void writeProperties(SPListItem li, string sectionTitle, params string[] keys)
    {
        using (System.IO.StreamWriter file = new System.IO.StreamWriter(_path, true))
        {
            file.WriteLine(sectionTitle);
            int i = 0;
 
            foreach (SPField f in li.Fields)
            {
                if (keys.Contains<string>(f.Title) || keys.Length == 0)
                {
                    string value = li[f.Title] != null ? ((object)li[f.Title]).ToString() : "Null";
                    string ph = string.Format("{3}\t {0}\t({1}) \t {2}", f.Title, f.Type.ToString(), value, i);
                    file.WriteLine(ph);
                }
                i++;
            }
            file.WriteLine("");
        }
    }
 
    /// <summary>
    /// Print out a list of values that you supply as a string array
    /// </summary>
    /// <param name="sectionTitle">Section title you want in the file</param>
    /// <param name="values">Array of string values you would like displayed in the file.</param>
    public void writeValues(string sectionTitle, params string[] values)
    {
        using (System.IO.StreamWriter file = new System.IO.StreamWriter(_path, true))
        {
            file.WriteLine(sectionTitle);
            int i = 0;
 
            foreach (string value in values)
            {
                file.WriteLine(string.Format("{0}\t{1}",i++, value));
            }
            file.WriteLine("");
        }
    }
 
    public void writeString(string key, string value)
    {
        using (System.IO.StreamWriter file = new System.IO.StreamWriter(_path, true))
        {
            file.WriteLine(string.Format("{0}: {1}",key,value));
                
            file.WriteLine("");
        }
    }
}
}