This post describes how to start workflows from SharePoint events. The details here are known to apply to SharePoint Designer workflows, but may also apply to Visual Studio 2005 workflows.
Purpose
Why would you want to do start a SharePoint workflow from a SharePoint event? One good reason is that the data available within a SharePoint Designer workflow may not contain sufficient information. In this situation there are several workarounds:
- Use a Visual Studio 2005 workflow instead.
- Do all of the work within a SharePoint event.
- Create custom SharePoint Designer workflow actions or conditions
- Provide the needed data by passing it into the workflow from an event.
The last item above is the focus of this post. All of the items above basically give full access to the SharePoint object model, but only the last one provides the flexibiliity and ease of access of SharePoint Designer workflows.
Starting a Workflow
(Note: This discusses SharePoint events, but describing how to set up SharePoint events is beyond the scope of this post.)
To start a workflow, you'll need to reference Microsoft.SharePoint and have a using statement (imports for VB.Net) for Microsoft.SharePoint and Microsoft.SharePoint.Workflow.
Within a SharePoint event (SPItemEventReceiver), you have access to the ListItem via SPItemEventProperites.ListItem. From there you can create a workflow manager and get the workflows associated with the list. The code below demonstrates how to do this (note that for simplicity all error handling, etc. has been removed from this code).
public class MyListUpdateReceiver : SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
this.StartWorkflow(properties, "Added");
}
public override void ItemUpdating(SPItemEventProperties properties)
{
base.ItemAdded(properties);
this.StartWorkflow(properties, "Updating");
}
private void StartWorkflow(SPItemEventProperties properties, string listEventType)
{
SPListItem item = properties.ListItem;
SPWorkflowManager workflowManager = item.Web.Site.WorkflowManager;
SPWorkflowAssociationCollection workflowAssocCol = item.ParentList.WorkflowAssociations;
foreach (SPWorkflowAssociation workflowAssoc in workflowAssocCol)
{
if (workflowAssoc.Name == "MyWorkflow")
{
workflowManager.StartWorkflow(item, workflowAssoc, String.Empty);
break;
}
}
}
}
Multiple Instances Running
When starting a workflow, you may need to make sure that no other instances of this are running. If a version of your workflow is already running for the particular list item, then you may not be able to start another instance. I apologize for not having the code available for this part...
Workflow Association Naming
Note that care should be taken in making sure you actually find the right workflow association. When you save workflows from SharePoint Designer when existing versions of the workflow already exist for a list, the behavior will work in one of two ways:
- The latest version of the workflow will be "MyWorkflow (n)" where "n" is a incremented version number.
- The latest version of the workflow will be "MyWorkflow" and the previous version will be renamed to something like "MyWorkflow (previous version at <date/time>)".
It's not clear how the system determines which behavior is used (this seems to be a bug). So, if the case is #1 for you, then you must modify your code each time to get it to work. If it is #2, then your code should work every time. The safest bet is to go to the list and delete all workflow association versions based on your workflow name (other completely different workflows can remain associated to the list) before you save any workflow changes in SharePoint designer.
If you dig a little further on this you will find that in the database for #1 above all workflows have the same vesion number and for #2 they have different version numbers. So it seems that #1 is likely a bug.
Initiation Parameters
So, you've started a workflow but this didn't really gain you anything. What you really want to do is pass in some information that is not available in a SharePoint Designer workflow. To do this you must define initiation parameters for your workflow. In our examples below we defined "EventType" and "After_Property1" as initiation parameters. The parameters are provided as XML. To make it easier to provide this, the following helper class can be used. You'll need to add a using statement for System.Collections.Specialized.
/// <summary>
/// Provides event data to a workflows in the format expected by workflows created in SharePoint Designer 2007.
/// </summary>
/// <remarks>
/// Example usage:
/// <code lang="c#">
/// // Package up the workflow event data
/// WorkflowEventData eventData = new WorkflowEventData();
/// eventData.Add("Param1", "Value1");
/// eventData.Add("Param2", "Value2");
///
/// // Start the workflow for an item/association pair and provide the event data
/// workflowManager.StartWorkflow(item, workflowAssoc, eventData.ToString());
/// </code>
/// </remarks>
public class WorkflowEventData : NameValueCollection
{
public WorkflowEventData()
{
}
/// <summary>
/// Outputs the name/value collection in the format expected by SharePoint Designer workflows.
/// </summary>
/// <returns></returns>
public override string ToString()
{
// The output looks like:
// <Data>
// <param1>value1</param1>
// <param2>value2</param2>
// </Data>
StringBuilder sb = new StringBuilder();
sb.Append("<Data>");
foreach (string key in this.Keys)
{
sb.Append(String.Format("<{0}>{1}</{2}>", key, this[key], key));
}
sb.Append("</Data>");
return sb.ToString();
}
}
Here is a modified version of our StartWorkflow() method showing how to pass data to your workflow using the WorkflowEventData helper class.
private void StartWorkflow(SPItemEventProperties properties, string listEventType)
{
SPListItem item = properties.ListItem;
SPWorkflowManager workflowManager = item.Web.Site.WorkflowManager;
SPWorkflowAssociationCollection workflowAssocCol = item.ParentList.WorkflowAssociations;
foreach (SPWorkflowAssociation workflowAssoc in workflowAssocCol)
{
if (workflowAssoc.Name == "MyWorkflow")
{
string afterProperty1 = properties.AfterProperties["Property1"].ToString();
WorkflowEventData eventData = new WorkflowEventData();
eventData.Add("EventType", listEventType.ToString());
eventData.Add("After_Property1", afterProperty1);
workflowManager.StartWorkflow(item, workflowAssoc, eventData.ToString());
break;
}
}
}
Make sure you understand the issues around before and after properties and how they act differently for synchronous and asynchronous events. Discussion on this is beyond the scope of this post.
Note also that when accessing fields within a list (e.g., "Property1" above), you need to use the internal name for the field. This can be determined using code, but can also be found using the U2U CAML Builder and can be downloaded from the U2U Community Tools Site.
Disclaimer
The code above was scrubbed to create an example suitable for this post. Extra content such as constants, error handling, references to specific list names, etc. has been removed. The code has been checked to ensure there were no compilation errors after the scrubbing exercise, but has not been tested after it was scrubbed.