Saturday, June 18, 2011

SharePoint 2010 Workflow: Dealing with events in custom activities

Introduction

In the past few months I've been responsible for designing a workflow solution to support the automation of an asset management acquisitions process for a customer in the US. The last time I worked on a large workflow project, which touched thousands of users and had a truly global reach, was back in December 2009. I was a little bit rusty and have spent one or two days in the last month getting back up to speed. In this blog post, I'd like to talk about a common scenario which will prove as much of a reference for me in the future as anything else.

The common scenario I'd like to deal with is one where there is a requirement to assign a task to users who will only be known at runtime. Further, when these tasks are assigned, depending upon the actual task/form which is in use, you may want to perform different actions.


The Requirements

  • Provide task management for 1 or more users who will only be known at runtime


  • Provide a customized summary report to an individual, who will only be known at runtime, which outlines what tasks were assigned, to whom, and what their response was.

If you are familiar with the MOSS 2007 ECM Starter Kit workflow examples then you have probably seen the widely referenced WssTaskActivity custom activity within the ECM Activities solution. This solution was fine for MOSS 2007 but I actually found it a little heavy in terms of code for a lot of scenarios. A much nicer, cleaner and relevent example is the one which has been put together by Scott Hillier. I would point you to his article at

http://www.shillier.com/archive/2010/08/04/CreatingMultipleandParallelTasks%20inSharePoint2010Workflow.aspx


and recommend you read that and become comfortable with the concepts before reading this article further.


The Problem

The main advantage of custom activities in workflow is to increase reusability and repeatability. Using the SPTaskActivity custom activity from the article posted above is a great starting point, but there's something missing. Quite often, what we need to do is provide some notification in the workflow which hosts the custom activity that something has happened e.g. a task has changed. It is possible to deal with all events relating to the custom activity within the custom activity itself but this doesn't provide reusability. We need to achieve this within the host.


The Solution

The solution to this problem lies in the use of DependencyProperty objects to raise a custom event within the host workflow.


  • Create a custom EventArgs class

  • Create a DependencyProperty which points to a generic EventHandler

  • Raise the event at the appropriate point e.g. onTaskChanged_Invoked from within your Custom Activity

  • Handle the event within your host workflow

Create a custom EventArgs class

First we need to decide what information we need to pass back to the host. For the purposes of this blog post, I've used the TaskEventArgs class from the MOSS 2007 ECM starter kit. The class is very simple and is given below. Create a new class file within your workflow project and paste this code.

[Serializable()]
public class TaskEventArgs : EventArgs
{
#region Private Variables

private SPWorkflowTaskProperties beforeProperties = null;
private SPWorkflowTaskProperties afterProperties = null;
private bool result = false;
private string executor = null;
private ExternalDataEventArgs externalArgs = null;

#endregion

#region Public Properties

public SPWorkflowTaskProperties BeforeProperties
{
get
{
return this.beforeProperties;
}
set
{
this.beforeProperties = value;
}
}

public SPWorkflowTaskProperties AfterProperties
{
get
{
return this.afterProperties;
}
set
{
this.afterProperties = value;
}
}

public bool Result
{
get
{
return this.result;
}
set
{
this.result = value;
}
}

public string Executor
{
get
{
return this.executor;
}
set
{
this.executor = value;
}
}

public ExternalDataEventArgs ExternalArgs
{
get
{
return this.externalArgs;
}
set
{
this.externalArgs = value;
}
}

#endregion

#region Constructors

public TaskEventArgs(SPWorkflowTaskProperties before, SPWorkflowTaskProperties after, string executor, ExternalDataEventArgs externalArgs)
{
this.beforeProperties = before;
this.afterProperties = after;
this.executor = executor;
this.externalArgs = externalArgs;
}

#endregion

}



Create a DependencyProperty which points to a generic EventHandler

This is pretty simple, and we can use code snippets to create this for us. You will want to ensure you specify that it is a generic EventHandler based on the TaskEventArgs class you've previously added to your workflow project.

public static DependencyProperty InvokeEvent = DependencyProperty.Register("Invoke", typeof(EventHandler<TaskEventArgs>), typeof(SPTaskActivity));

[Description("Invoke")]
[Category("Invoke Category")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public event EventHandler<TaskEventArgs> Invoke
{
add
{
base.AddHandler(SPTaskActivity.InvokeEvent, value);
}
remove
{
base.RemoveHandler(SPTaskActivity.InvokeEvent, value);
}
}


The result should look something like the below






Raise the event at the appropriate point e.g. onTaskChanged_Invoked from within your Custom Activity

The next thing we need to do is actually raise this event. In this instance, I wanted the Custom Activity to really do nothing when onSPTaskChanged_Invoked was fired, I needed this to be handled externally by the host workflow. We can achieve this by first raising the event in the following way:

private void onSPTaskChanged_Invoked(object sender, ExternalDataEventArgs e)
{
TaskEventArgs args = new TaskEventArgs(SPBeforeProperties,
SPAfterProperties, null, e);

base.RaiseGenericEvent<TaskEventArgs>(InvokeEvent, this, args);
}

Handle the event within your host workflow

We then need to handle the event in the workflow. Inspect the properties of your custom activity and notice you now have a new event which you can handle.

private void InvokedFromSPTaskActivity(object sender, TaskEventArgs e)
{
bool result = Boolean.Parse(e.AfterProperties.ExtendedProperties["Result"].ToString());
}

The result should look something like the below:





Summary

We have seen how we can leverage a DependencyProperty based generic EventHandler to add more reusability and flexibility to our custom activities within workflow. This is an important technique and one that, at first, may not seem straight-forward but is surprisingly simple.

No comments:

Post a Comment