Sharepoint Windows Workflow Foundation: Passing the Workflow Correlation Token to a Custom Activity

In windows workflow foundation, you can create custom activities that contain a subset of your workflow activities. 

By default, the custom activity does not have access to the workflow correlation token which is created in the main workflow and used by the OnWorkflowActivated activity at the start of the main workflow.

However, there are some activities within the custom activity that you may want to use that need to use the workflow correlation token to work.  The SendEmail activity, and the OnWorkflowItemChanged are two activities that require the use of the workflow correlation token. 

Check out the following post for a nice article on correlation tokens.
http://sharepoint.microsoft.com/blog/Pages/BlogPost.aspx?PageType=4&ListId={72C1C85B-1D2D-4A4A-90DE-CA74A7808184}&pID=863

I had found the following post with a sample of how this could be done, but the code listed in the post wasn't complete, and it took me several hours to figure out how to get it working.
http://blog.sharepoint.ch/2009/11/how-to-set-correlation-token-property.html

 Therefore, I've decided to write my own blog post on how to do it.

Step 1: Create a dependency property to receive the workflow token in your custom activity.

    public static DependencyProperty WorkflowTokenProperty = DependencyProperty.Register("WorkflowToken", typeof(CorrelationToken), typeof(ApprovalTask));

        [DescriptionAttribute("WorkflowToken")]
        [CategoryAttribute("WorkflowToken Category")]
        [BrowsableAttribute(true)]
        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [DefaultValue((string)null), TypeConverter("System.Workflow.Activities.CorrelationTokenTypeConverter, System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
        public CorrelationToken WorkflowToken
        {
            get
            {
                return ((CorrelationToken)(base.GetValue(ApprovalTask.WorkflowTokenProperty)));
            }
            set
            {   onWorkflowItemChanged1.CorrelationToken = value;                //sendEmail1.CorrelationToken = value;

                base.SetValue(ApprovalTask.WorkflowTokenProperty, value);
            
            }
        }

In design view, you will have to set the CorrelationToken for the activity (onWorkflowItemChanged1 in the above example) to a dummy correlation token with the Owner Activity for the token set to the custom activity.  This value will be replaced by the Workflow Token when the WorkflowToken dependency variable is set.  

One of the big issues, I had was figuring out where and when to set the WorkflowToken dependency variable  from the workflow.


Step 2: Add a way to test that the CorrelationToken for the activity was set correctly.

In my case, I added a LogToHistoryList Activity, and created  a field for the description and outcome. I then added a code activity with the following code to set the values for the LogToHistoryListActivity:

  private void codeOnWorkflowItemChangedBeforeCode_ExecuteCode(object sender, EventArgs e)
        {
           // onWorkflowItemChanged1.CorrelationToken = WorkflowToken;
            logToHistoryListActivity4_HistoryDescription1 = "Initiating listen event for OnWorkflowItemChanged.  Activity Correlation Token Info: " + onWorkflowItemChanged1.CorrelationToken.Name + ", " + onWorkflowItemChanged1.CorrelationToken.OwnerActivityName;

            if (WorkflowToken != null)
            {
                logToHistoryListActivity4_HistoryDescription1 += "; Workflow Token is Set: " + WorkflowToken.OwnerActivityName;
               // onWorkflowItemChanged1.CorrelationToken = WorkflowToken;

            }
            else
            {
                logToHistoryListActivity4_HistoryDescription1 += "; Workflow Token is null.";
            }
               
            logToHistoryListActivity4_HistoryOutcome1 = "Success";
        }



The above code will basically add a history item that shows whether the onWorkflowItemChanged1 is using the correct correlation token.

3) You can optionally add a validation entry for the WorkflowToken dependency variable, so that it is mandatory to bind this dependency variable at design time.

 public class ApprovalTaskActivityValidator : ActivityValidator
    {

        public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
        {
            ApprovalTask task = obj as ApprovalTask;

            ValidationErrorCollection activityErrors = base.Validate(manager, obj);
                       /*
            * If you have Dependency Properties, you need to check them a little differently, using IsBindingSet and GetValue:
            */
            if ((null != task) && (null != task.Parent))
            {
                if (!task.IsBindingSet(ApprovalTask.CustomApprovalContentTypeIdProperty))
                {
                    if (task.GetValue(ApprovalTask.CustomApprovalContentTypeIdProperty) == null)
                    {
                        activityErrors.Add(ValidationError.GetNotSetValidationError("CustomApprovalContentTypeIdProperty"));
                    }
                }

                if (!task.IsBindingSet(ApprovalTask.WorkflowPropertiesProperty))
                {
                    if (task.GetValue(ApprovalTask.WorkflowPropertiesProperty) == null)
                    {
                        activityErrors.Add(ValidationError.GetNotSetValidationError("WorkflowPropertiesProperty"));
                    }
                }

                if (!task.IsBindingSet(ApprovalTask.WorkflowTokenProperty))
                {
                    if (task.GetValue(ApprovalTask.WorkflowTokenProperty) == null)
                    {
                        activityErrors.Add(ValidationError.GetNotSetValidationError("WorkflowTokenProperty"));
                    }
                }
            }

            return activityErrors;
        }
    }


4) Now, in the workflow that consumes the custom activity, you must bind the dependency variable to onWorkflowActivated.CorrelationToken for each instance of the custom activity.

5) However, doing this at design time, proved to not be enough to set the workflow correlation token within the custom activity.

The history log event kept showing that though the Workflow Token had been passed via the dependency variable, the custom activity was still using the dummy correlation token.

Initiating listen event for OnWorkflowItemChanged.  Activity Correlation Token Info: DummyCorrelationToken, ApprovalTask; Workflow Token is Set: EWWApprovalWorkflow

Further, if you tried to set the Correlation Token in code prior to calling the OnWorkflowItemChanged activity, you would get an error indicating that you can not set the variable at run time. 

Fortunately, after reading the article mentioned above again, I noticed he indicated that he had set the variables in the constructor of the workflow.  So I gave that a try, and it worked.  So for each instance of the custom activity, you must set the WorkflowToken dependency variable explicitly in the constructor of the workflow, as well.

  public EWWApprovalWorkflow()
        {
            InitializeComponent();

            supervisorApprovalTask.WorkflowToken = onWorkflowActivated.CorrelationToken;
            budgetApprovalTask.WorkflowToken = onWorkflowActivated.CorrelationToken;
        }


I was then able to successfully use the Workflow Correlation Token from within a Custom Activity!

The history list displayed the results I was hoping for, and the onWorkflowItemChanged event triggered ok.


Initiating listen event for OnWorkflowItemChanged.  Activity Correlation Token Info: workflowToken, EWWApprovalWorkflow; Workflow Token is Set: EWWApprovalWorkflow

Comments

  1. Do you have a copy of your code? I'm getting stuck on "This operation can not be performed at runtime" even with my code in the constructor of the custom activity.

    ReplyDelete
    Replies
    1. Try commenting out the line where you set the correlation token.

      It may be that you tried to set it somewhere else in your code other than the constructor, which is causing the error.

      Delete

Post a Comment

Popular posts from this blog

Getting Authentication Prompt When Accessing SharePoint Web Services

How To use ASPNET_SetReg to store encrypted data in the registry and then decrypt the data for use in your app

PowerShell Script to Clean the Windows Installer Directory