Using nested StateActivity to send regular reminders in Visual Studio SharePoint State Machine Workflow


Note. I want to thank Daniel Odievich for providing the content of this post, a must read!

When developing SharePoint workflows, one often needs to send email notification for a task, and then keep sending regular reminders for this task at scheduled intervals. When developing workflows in SharePoint Designer, one can use DelayUntilActivity and DelayForActivity, but those were not designed for in Visual Studio workflows.

When using State Machine workflows, the implementation for regular reminders is quite different than that in Sequential workflow. One can use put in EventDrivenActivity with DelayActivity but it will only execute once so you can only have one-time reminder, instead of regular reminders. If you try to wrap DelayActivity in a WhileActivity, the EventDrivenActivity won’t compile because its first child must be IEventActivity (such as DelayActivity).

Unlike with the Sequential workflow, ConditionedActivityGroupActivity cannot be used in State machine EventDrivenActivity. Also, one cannot write a loop around DelayActivity as it is supposed to be the first child of EventDrivenActivity.

The approach that can be taken is to use WF ability to reenter the same StateActivity over and over and thus reset the workflow. It is suggested here http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=3553632&SiteID=1 but the following discussion adopts it for the SharePoint reality of task correlation tokens.

One common way to use StateMachineActivity in SharePoint is to create a task in StateInitializationActivity, register for OnTaskChanged or OnItemChanged event using EventDrivenActivity and use EventDrivenActivity with DelayActivity to send reminders. When using State Machine and SharePoint tasks, you most likely expect to reenter this state over and over, creating more tasks of the same kind (one common scenario is multi-stage approval with ability to send back the approval to previous people for re-approval). If you expect to reenter this task, you must set the all-important correlation tokens for the task should be set to use the containing StateActivity as a parent. Following is the structure you might create:

<StateActivity Name=“MyCustomTaskState”>

<StateInitializationActivity>

<CreateTask CorrelationTokenParent=“MyCustomTaskState” />

<SendEmail Type=“Task Created” />

</StateInitializationActivity>

<EventDrivenActivity Type=“Task Changed”>

<OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” />

<UpdateTask CorrelationTokenParent=“MyCustomTaskState” />

<IfElseActivity>

<IfElseBranch Condition=”Task Completed”>

<SetStateActivity Target=“OtherState” />

<IfElseBranch />

<IfElseActivity />

</EventDrivenActivity>

<EventDrivenActivity Type=“Item Changed”>

<OnItemChanged />

<IfElseActivity>

<IfElseBranch Condition=”Task Completed”>

<SetStateActivity Target=“OtherState” />

<IfElseBranch />

<IfElseActivity />

</EventDrivenActivity>

<EventDrivenActivity Type=“Reminder”>

<DelayActivity />

<SendEmail Type=“Reminder” />

<SetStateActivity Target=“MyCustomTaskState” />

</EventDrivenActivity>

<StateFinalizationActivity>

<CompleteTask CorrelationTokenParent=“MyCustomTaskState” />

<SendEmail Type=“Task Completed” />

</StateFinalizationActivity>

<StateActivity />

If you run this code, you will notice the following sequence of events:

1. <CreateTask CorrelationTokenParent=“MyCustomTaskState” />

2. <SendEmail Type=“Task Created” />

3. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register

4. <OnItemChanged /> - register

5. <DelayActivity /> - sleep

The workflow will then go to sleep and wait for either OnTaskChanged event, OnItemChanged event or for DelayActivity to elapse.

If OnTaskChanged or OnItemChange event wakes up workflow before DelayActivity has ever elapsed, and the user action causes workflow to move to another state everything is going to run correctly. However, once DelayActivity elapses, it wakes up and the following sequence of events occurs:

1. <DelayActivity /> - wake

2. <SendEmail Type=“Reminder” />

3. <SetStateActivity Target=“MyCustomTaskState” />

4. <CompleteTask CorrelationTokenParent=“MyCustomTaskState” />

5. <SendEmail Type=“Task Completed” />

6. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register

7. <CreateTask CorrelationTokenParent=“MyCustomTaskState” />

8. <SendEmail Type=“Task Created” />

9. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register

10.<OnTaskChanged /> - register

11.<DelayActivity /> - sleep

As you can see, your task will be completed and immediately new task of the same type will be created and new notification emails will be sent. If you sent link to the original task in the emails, the URLs in the email are no longer valid.

To avoid this, you might be tempted to wrap the activities in StateInitializationActivity and StateFinalizationActivity to avoid completing and recreating the task. If you do, you will discover that after reentering the state, OnTaskChanged events will no longer register correctly with exception indicating that correlation token for the task has not been initialized. This is because the correlation tokens use the StateActivity as a parent, and when it is reentered, they are reset, and you have orphaned your original task. There is no way to preserve those correlation tokens from one entry of StateActivity to another. A different approach is needed.

The correct approach is to use ability of StateActivity to host another StateActivity and break up task creation, task completion and task events into three separate StateActivity entities, and set the correlation token to use the top-most parent StateActivity:

<StateActivity Name=“MyCustomTaskState”>

<StateActivity Name=“CreateTaskState”>

<StateInitializationActivity>

<CreateTask CorrelationTokenParent=“MyCustomTaskState” />

<SendEmail Type=“Task Created” />

<SetStateActivity Target=“TaskEventsState” />

</StateInitializationActivity>

<StateActivity />

<StateActivity Name=“TaskEventsState”>

<EventDrivenActivity Type=“Task Changed”>

<OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” />

<UpdateTask CorrelationTokenParent=“MyCustomTaskState” />

<IfElseActivity>

<IfElseBranch Condition=“Task Completed”>

<SetStateActivity Target=“CompleteTaskState” />

<IfElseBranch />

<IfElseActivity />

</EventDrivenActivity>

<EventDrivenActivity Type=“Item Changed”>

<OnItemChanged />

<SetStateActivity Target=“TaskEventsState” />

<IfElseActivity>

<IfElseBranch Condition=“Task Completed”>

<SetStateActivity Target=“CompleteTaskState” />

<IfElseBranch />

<IfElseActivity />

</EventDrivenActivity>

<EventDrivenActivity Type=“Reminder”>

<DelayActivity />

<SendEmail Type=“Reminder” />

<SetStateActivity Target=“TaskEventsState” />

</EventDrivenActivity>

<StateActivity />

<StateActivity Name=“CompleteTaskState”>

<StateInitializationActivity>

<CompleteTask CorrelationTokenParent=“MyCustomTaskState” />

<SendEmail Type=“Task Completed” />

<IfElseActivity>

<IfElseBranch Condition=“Task Completed”>

<SetStateActivity Target=“OtherState” />

<IfElseBranch />

<IfElseActivity />

</StateInitializationActivity>

<StateActivity />

<StateActivity />

If you run this code, you will notice the following sequence of events:

1. <CreateTask CorrelationTokenParent=“MyCustomTaskState” />

2. <SendEmail Type=“Task Created” />

3. <SetStateActivity Target=“TaskEventsState” />

4. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register

5. <OnItemChanged /> - register

6. <DelayActivity /> - sleep

The workflow will then go to sleep and wait for either OnTaskChanged event, OnItemChanged event or for DelayActivity to elapse. Once DelayActivity elapses, following sequence of events occurs:

1. <DelayActivity /> - wake

2. <SendEmail Type=“Reminder” />

3. <SetStateActivity Target=“MyCustomTaskState” />

4. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> - register

5. <OnTaskChanged /> - register

6. <DelayActivity /> - sleep

Whenever the task change occurs that indicates that workflow is ready to move to another state, the following sequence of events occurs:

1. <OnTaskChanged CorrelationTokenParent=“MyCustomTaskState” /> or

2. <UpdateTask CorrelationTokenParent=“MyCustomTaskState” />

3. <IfElseActivity>

4. <IfElseBranch Condition=“Task Completed”>

5. <SetStateActivity Target=“CompleteTaskState” />

6. <CompleteTask CorrelationTokenParent=“MyCustomTaskState” />

7. <SendEmail Type=“Task Completed” />

8. <IfElseActivity>

9. <IfElseBranch Condition=“Task Completed”>

10.<SetStateActivity Target=“OtherState” />

Whenever the item change occurs that indicates that workflow is ready to move to another state, the following sequence of events occurs:

1. <OnItemChanged />

2. <SetStateActivity Target=“TaskEventsState” />

3. <IfElseActivity>

4. <IfElseBranch Condition=“Task Completed”>

5. <SetStateActivity Target=“CompleteTaskState” />

6. <CompleteTask CorrelationTokenParent=“MyCustomTaskState” />

7. <SendEmail Type=“Task Completed” />

8. <IfElseActivity>

9. <IfElseBranch Condition=“Task Completed”>

10.<SetStateActivity Target=“OtherState” />

The workflow designer outline of this is shown below:

clip_image002

Overall view of the parent state

clip_image004

Overall hierarchy of all activities

clip_image006

CreateTaskState with creation of the task and events

clip_image008

Properties window of CreateTask activity with correlation token pointing to parent state of all three activities

clip_image010

EventDrivenActivity for OnTaskChanged event

clip_image012

EventDrivenActivity for OnItemChanged event

clip_image014

EventDrivenActivity for DelayActivity and reminder email

clip_image016

CompleteTaskState and transition to another state

Comments (6)

  1. Cango says:

    Hello Ali,

    do you know if it is possible to modify a Workflow from the InfoPath Code?

    I want to add a DelayActivity dynamically to the Workflow from the VSTA Code.

    I tried using the WorflowRuntime but i dont get the WorkflowInstance correctly.

    Any idea if this will work out from "outside"?

    Greetings and thanks

  2. Jeff says:

    I’ve been trying to figure out how to dynamically set the correlation token when a new task of the same type was created, but evidently I was looking at it wrong.  Thanks for this post, it helped me a ton.

  3. Chen says:

    This is a very nice post. Can you please let me know how to create multiple tasks in State Machine workflow. I could not find help anywhere.

  4. chen says:

    Jeff

         Can yu please let me know how you resolved it

  5. Fire148 says:

    Just seen this post and it is extremely useful – I was close to this, but missing the final piece of the jigsaw!

Skip to main content