02 November 2009

How to set a Correlation Token Property on a Custom Activity


I like to break up my sharepoint workflows into several custom activities, especially when there are many repetitive tasks involved. But I've always struggeld with one thing: when I needed to send an email from such an activity, I had to pass the correlation token of the onWorkflowActivated activity manually in the constructor of my workflow to my custom activity, which assigned it in turn to the SendEmail activity. As one doesn't get workflow validation using this approach, it makes for an easy to oversee failure if in a hurry. And additionally, the compiler produces a warning that the correlation token of you SendEmail Activity may be uninitialized.

After a little digging around with Reflector.NET, I finally found out that one can declare a Type Converter of the type CorrelationTokenTypeConverter on the correlation token property of the custom activity. As this type converter is an internal class, the type property of the attribute has to be set by its full qualified class name.

[DefaultValue((string)null),
TypeConverter("System.Workflow.Activities.CorrelationTokenTypeConverter, System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public CorrelationToken WorkflowCorrelationToken
{
get { return (CorrelationToken)base.GetValue(WorkflowCorrelationTokenProperty); }
set
{
base.SetValue(WorkflowCorrelationTokenProperty, value);
sendEmail.CorrelationToken = value;
}
}

(The code snippets above assumes assumes that a “CorrelationToken” dependency property is registered for the custom activity class.)

Now that we can set the correlation token using the designer, we are also able to use a validator class on the custom activity, so the compiler will throw an error if the correlation token is not set. Doing this is quite easy, just add the ActivityValidator attriubte to the activity class definition and implement an activityvalidator class which validates that the correlation token property of the custom activity is set.

[ActivityValidator(typeof(MyActivityValidator))]
public partial class MyActivity : SequenceActivity
{

private class MyActivityValidator : ActivityValidator
{
/// <summary>
/// Verifies that the MyActivity activity is valid.
/// </summary>
/// <param name="manager">The <see cref="T:System.Workflow.ComponentModel.Compiler.ValidationManager"/> associated with validation.</param>
/// <param name="obj">The <see cref="T:System.Workflow.ComponentModel.Activity"/> to be validated.</param>
/// <returns>
/// A <see cref="T:System.Workflow.ComponentModel.Compiler.ValidationErrorCollection"/> object containing any errors or warnings that occurred during validation.
/// </returns>
public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
{
ValidationErrorCollection errors = new ValidationErrorCollection();
MyActivity activity = obj as MyActivity;
if (activity == null)
{
throw new InvalidOperationException();
}
if (activity.Parent != null)
{
if (activity.WorkflowCorrelationToken == null)
{
Hashtable hashtable = activity.GetValue(WorkflowMarkupSerializer.ClrNamespacesProperty) as Hashtable;
if ((hashtable == null) (hashtable["WorkflowCorrelationToken"] == null))
{
errors.Add(ValidationError.GetNotSetValidationError("WorkflowCorrelationToken"));
}
}
}
errors.AddRange(base.Validate(manager, obj));
return errors;
}
}
// SNIP: MyActivity code...
}

9 Kommentare:

Clarity hat gesagt…

Thank you for this! I can now create a property in customactivity and assign it in the main workflow, however I don't know how to assign this to the setstate correlation token that I use in my custom activity?

Rolf Bänziger hat gesagt…

@Clarity
There are two things you have to do: First, you have to set a dummy correlation token on your setState activity, so your code compiles.

Second, to make the setState activity use the correct correlation token (the workflow token) you have to modify the CorrelationToken property of your Custom Activity so that it sets the property AND the CorrelationToken property of setState Activity:
set
{
// set the property
base.SetValue(WorkflowCorrelationTokenProperty, value);
// set the correlation token property of the setState Activity
setStateActivity.CorrelationToken = value;
}

Grank hat gesagt…

I don't understand where you initialize your correlation tokens in this example. I see how they're passed around and how you can push the validation to design-time, but if you initialize them in the constructor it's too late for that to work in the designer... I'm new to this CorrelationToken thing and am a little lost.

Rolf Bänziger hat gesagt…

@Grank: The example above shows the skeleton of a custom developed workflow activity, which you can use in your "main" workflow. To initialize the workflow token, just set the CorrelationToken property of the onWorkflowActivated Activity of the main workflow. You can then reuse this token for any other activities that require the workflow token (the code above makes it possible to set this token on a custom activity, too).

Technically, the workflow token is initialized in the designer-generated InitializeComponent() method.

Please see also this excellent post of the MS SharePoint Team Blog about when to use which workflow tokens: Developing Workflows in VS: Part 3 - Five Steps for Developing Your Workflow

Grank hat gesagt…

Ah, I think I see the problem. I'm not trying to write a whole workflow in VS, I'm just trying to write a Workflow Activity Library in VS and use a custom action in a workflow in the Sharepoint Designer. Which doesn't give me the option of setting a CorrelationToken anywhere that I can see.
The way I have it now, it shows up in the Actions menu of the Sharepoint Designer workflow creator, and I can pick it and save the workflow on a list and it looks great. But it doesn't work.
When the workflow is triggered, I hit the breakpoint in the Activity's constructor 11 times in a row for some reason, but never even get as far as the Invoked handler on the Microsoft.Sharepoint.WorkflowActions.OnWorkflowActivated node that's the first thing in my activity. So it's like it's triggering the activity but not running it. The workflow will forever sit in "In Progress". Seems like the kind of thing that could be related to CorrelationToken stuff?

Rolf Bänziger hat gesagt…

@Grank Well, this will not work. There must be only one onWorkflowActivated Activity right at the begining of the workflow. If you create a Workflow with the SharePoint Designer, a onWorkflowActivated Activity is added to the generated workflow automatically. Any further onWorkflowActivated Activity will never be hit and block the workflow indefinitly.

As to how to get the correlation token to be set by a sharepoint designer workflow, I don't know.

Grank hat gesagt…

Heh yeah, I figured that out right after posting my last comment. I'm new to Sharepoint workflows in general, and the first thing I read was that every Sharepoint workflow must start with OnWorkflowActivated... So I put one in without thinking.
It's working fine now. Thanks for the help!

Unknown hat gesagt…

Thanks for a great post! I'd been struggling with how to pass a correlation token to a child activity of my custom activity for a while now. Your code helped me put the last piece together.

Unknown hat gesagt…

All,

I am also struggling with this issue. In my scenario, I have a custom activity (jobSetupActivity1) inside a replicator that contains a send email activity(sendEmailForSetupTask). I need to pass a reference to the "WorkflowToken" into my custom activity in order to bind it to the sendEmail activity. As suggested, I created a dependency property in my custom activity class named ParentToken, and I set this property to "WorkflowToken" in the workflow designer. I then set the sendEmail activity's CorrelationToken property to this.ParentToken in the custom activity designer.

After the first iteration of the replicator completes, I catch the following exception in my faultHandler:

System.InvalidOperationException: Correlation value has not been initialized on declaration this.ParentToken for activity jobSetupActivity1.sendEmailForSetupTask.
at Turner.JobOpening.Workflow1.codeLogException_ExecuteCode(Object sender, EventArgs e)
at System.Workflow.ComponentModel.Activity.RaiseEvent(DependencyProperty dependencyEvent, Object sender, EventArgs e)
at System.Workflow.Activities.CodeActivity.Execute(ActivityExecutionContext executionContext)
at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)
at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext)
at System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime)

Has anyone else had this issue? How did you overcome it?

Thanks,

Kenny